contextsynapse 1.0.0__py3-none-any.whl
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.
- contextsynapse/__init__.py +107 -0
- contextsynapse/__main__.py +9 -0
- contextsynapse/a2a/__init__.py +28 -0
- contextsynapse/a2a/client.py +120 -0
- contextsynapse/a2a/discovery.py +128 -0
- contextsynapse/a2a/handlers.py +164 -0
- contextsynapse/a2a/models.py +246 -0
- contextsynapse/a2a/streaming.py +92 -0
- contextsynapse/a2a/task_manager.py +275 -0
- contextsynapse/adapters/__init__.py +98 -0
- contextsynapse/adapters/_base.py +298 -0
- contextsynapse/adapters/autogen/__init__.py +4 -0
- contextsynapse/adapters/autogen/tools.py +34 -0
- contextsynapse/adapters/crewai/__init__.py +4 -0
- contextsynapse/adapters/crewai/tools.py +63 -0
- contextsynapse/adapters/langchain/__init__.py +19 -0
- contextsynapse/adapters/langchain/message_history.py +17 -0
- contextsynapse/adapters/langchain/retriever.py +158 -0
- contextsynapse/adapters/langchain/tools.py +43 -0
- contextsynapse/adapters/langgraph/__init__.py +21 -0
- contextsynapse/adapters/langgraph/checkpoint.py +223 -0
- contextsynapse/adapters/langgraph/context_tools.py +341 -0
- contextsynapse/adapters/langgraph/message_history.py +138 -0
- contextsynapse/adapters/langgraph/tools.py +43 -0
- contextsynapse/adapters/llamaindex/__init__.py +4 -0
- contextsynapse/adapters/llamaindex/tools.py +64 -0
- contextsynapse/adapters/openai/__init__.py +24 -0
- contextsynapse/adapters/openai/tools.py +206 -0
- contextsynapse/adapters/pydantic_ai/__init__.py +4 -0
- contextsynapse/adapters/pydantic_ai/tools.py +36 -0
- contextsynapse/adapters/swarm/__init__.py +4 -0
- contextsynapse/adapters/swarm/tools.py +44 -0
- contextsynapse/agents/__init__.py +6 -0
- contextsynapse/agents/auto_capture.py +80 -0
- contextsynapse/agents/dispatcher.py +299 -0
- contextsynapse/agents/notifications.py +112 -0
- contextsynapse/aiql/__init__.py +71 -0
- contextsynapse/aiql/cache.py +341 -0
- contextsynapse/aiql/compiler/__init__.py +62 -0
- contextsynapse/aiql/compiler/optimizer.py +126 -0
- contextsynapse/aiql/compiler/planner.py +366 -0
- contextsynapse/aiql/compiler/statistics.py +235 -0
- contextsynapse/aiql/compiler/validator.py +425 -0
- contextsynapse/aiql/engine/__init__.py +11 -0
- contextsynapse/aiql/engine/chunk_storage.py +2584 -0
- contextsynapse/aiql/engine/executor.py +7737 -0
- contextsynapse/aiql/engine/extraction_integration.py +442 -0
- contextsynapse/aiql/engine/graph_builder.py +405 -0
- contextsynapse/aiql/engine/node_creation_from_normalized.py +691 -0
- contextsynapse/aiql/engine/performance_monitor.py +289 -0
- contextsynapse/aiql/engine/postprocessing.py +365 -0
- contextsynapse/aiql/engine/ray_stage_executors.py +918 -0
- contextsynapse/aiql/engine/redis_cache.py +106 -0
- contextsynapse/aiql/engine/streaming_json.py +366 -0
- contextsynapse/aiql/engine/streaming_parquet.py +568 -0
- contextsynapse/aiql/engine/streaming_ray_chunking.py +419 -0
- contextsynapse/aiql/grammar/__init__.py +14 -0
- contextsynapse/aiql/grammar/aiql_grammar.py +1822 -0
- contextsynapse/aiql/grammar/base.py +71 -0
- contextsynapse/aiql/parser/__init__.py +46 -0
- contextsynapse/aiql/parser/aiql_parser.py +11330 -0
- contextsynapse/aiql/parser/base_parser.py +47 -0
- contextsynapse/api/__init__.py +5 -0
- contextsynapse/api/a2a_router.py +233 -0
- contextsynapse/api/admin_router.py +140 -0
- contextsynapse/api/agent_worker_router.py +978 -0
- contextsynapse/api/algorithms_router.py +317 -0
- contextsynapse/api/api.py +4144 -0
- contextsynapse/api/audit.py +204 -0
- contextsynapse/api/auth.py +441 -0
- contextsynapse/api/auth_router.py +537 -0
- contextsynapse/api/billing.py +261 -0
- contextsynapse/api/billing_router.py +275 -0
- contextsynapse/api/boundary_router.py +452 -0
- contextsynapse/api/cognition_router.py +116 -0
- contextsynapse/api/connector_router.py +237 -0
- contextsynapse/api/context_router.py +1619 -0
- contextsynapse/api/dashboard_router.py +11367 -0
- contextsynapse/api/errors.py +183 -0
- contextsynapse/api/events.py +88 -0
- contextsynapse/api/experiment_bootstrap.py +321 -0
- contextsynapse/api/experiment_orchestrator.py +1078 -0
- contextsynapse/api/experiment_router.py +2089 -0
- contextsynapse/api/feature_gate.py +89 -0
- contextsynapse/api/federation_router.py +108 -0
- contextsynapse/api/integrations.py +567 -0
- contextsynapse/api/integrations_router.py +461 -0
- contextsynapse/api/intelligence_router.py +1028 -0
- contextsynapse/api/metering.py +155 -0
- contextsynapse/api/metrics.py +112 -0
- contextsynapse/api/models.py +38 -0
- contextsynapse/api/monitoring_router.py +174 -0
- contextsynapse/api/pipeline_router.py +1437 -0
- contextsynapse/api/playground_conn.py +116 -0
- contextsynapse/api/projects_router.py +131 -0
- contextsynapse/api/queries_router.py +282 -0
- contextsynapse/api/quota.py +95 -0
- contextsynapse/api/rate_limit.py +64 -0
- contextsynapse/api/search_router.py +261 -0
- contextsynapse/api/shield_router.py +447 -0
- contextsynapse/api/tenant_guard.py +103 -0
- contextsynapse/api/tenant_quotas.py +231 -0
- contextsynapse/api/tenants.py +305 -0
- contextsynapse/api/users.py +646 -0
- contextsynapse/api/v1.py +167 -0
- contextsynapse/api/vertical_builder_router.py +387 -0
- contextsynapse/benchmark/__init__.py +15 -0
- contextsynapse/benchmark/__main__.py +64 -0
- contextsynapse/benchmark/agents.py +338 -0
- contextsynapse/benchmark/live_ab.py +338 -0
- contextsynapse/benchmark/reporter.py +149 -0
- contextsynapse/benchmark/runner.py +264 -0
- contextsynapse/benchmark/savings.py +266 -0
- contextsynapse/benchmark/token_counter.py +212 -0
- contextsynapse/benchmark/workloads.py +289 -0
- contextsynapse/buffer/__init__.py +154 -0
- contextsynapse/buffer/base.py +257 -0
- contextsynapse/buffer/duckdb_buffer.py +246 -0
- contextsynapse/buffer/file_buffer.py +229 -0
- contextsynapse/buffer/lmdb_buffer.py +286 -0
- contextsynapse/buffer/local_buffer.py +154 -0
- contextsynapse/buffer/redis_buffer.py +211 -0
- contextsynapse/buffer/sqlite_buffer.py +266 -0
- contextsynapse/cache.py +2 -0
- contextsynapse/cli/__init__.py +40 -0
- contextsynapse/cli/cli.py +475 -0
- contextsynapse/cli/config_cli.py +177 -0
- contextsynapse/cli/scaffold.py +465 -0
- contextsynapse/cognition/__init__.py +31 -0
- contextsynapse/cognition/derivation.py +55 -0
- contextsynapse/cognition/events.py +135 -0
- contextsynapse/cognition/feedback.py +52 -0
- contextsynapse/cognition/hallucination.py +90 -0
- contextsynapse/cognition/invalidation.py +70 -0
- contextsynapse/cognition/read_tracker.py +50 -0
- contextsynapse/collaboration.py +2 -0
- contextsynapse/comparison.py +2 -0
- contextsynapse/config/__init__.py +10 -0
- contextsynapse/config/connectors/amfi_nav.yaml +18 -0
- contextsynapse/config/connectors/auto_sector.yaml +20 -0
- contextsynapse/config/connectors/banking_sector.yaml +20 -0
- contextsynapse/config/connectors/energy_sector.yaml +20 -0
- contextsynapse/config/connectors/fmcg_sector.yaml +20 -0
- contextsynapse/config/connectors/nsdl_fpi.yaml +9 -0
- contextsynapse/config/connectors/nse_price.yaml +61 -0
- contextsynapse/config/connectors/pharma_sector.yaml +20 -0
- contextsynapse/config/connectors/rbi_dbie.yaml +12 -0
- contextsynapse/config/context_presets/ai_research.json +25 -0
- contextsynapse/config/context_presets/chatgpt_conversations.json +19 -0
- contextsynapse/config/context_presets/claude_conversations.json +19 -0
- contextsynapse/config/context_presets/legal_compliance.json +21 -0
- contextsynapse/config/context_presets/meeting_notes.json +21 -0
- contextsynapse/config/context_presets/slack_engineering.json +21 -0
- contextsynapse/config/context_presets/tech_climate.json +24 -0
- contextsynapse/config/context_presets/toi_politics.json +25 -0
- contextsynapse/config/database_config.py +453 -0
- contextsynapse/config/deployment.py +221 -0
- contextsynapse/config/gpt_action_schema.json +164 -0
- contextsynapse/config/indicators/india_macro.yaml +213 -0
- contextsynapse/config/logging_config.py +88 -0
- contextsynapse/config/mode_config.py +223 -0
- contextsynapse/config/schemas/api_spec.yaml +48 -0
- contextsynapse/config/schemas/architecture_doc.yaml +51 -0
- contextsynapse/config/schemas/chat_history.yaml +90 -0
- contextsynapse/config/schemas/chatgpt_conversation.yaml +101 -0
- contextsynapse/config/schemas/claude_conversation.yaml +148 -0
- contextsynapse/config/schemas/contract.yaml +48 -0
- contextsynapse/config/schemas/conversation.yaml +167 -0
- contextsynapse/config/schemas/database.yaml +74 -0
- contextsynapse/config/schemas/decision.yaml +72 -0
- contextsynapse/config/schemas/ecommerce.yaml +56 -0
- contextsynapse/config/schemas/education.yaml +42 -0
- contextsynapse/config/schemas/healthcare.yaml +133 -0
- contextsynapse/config/schemas/hr_document.yaml +48 -0
- contextsynapse/config/schemas/invoice.yaml +40 -0
- contextsynapse/config/schemas/knowledge_base.yaml +75 -0
- contextsynapse/config/schemas/legal.yaml +42 -0
- contextsynapse/config/schemas/marketing_content.yaml +48 -0
- contextsynapse/config/schemas/meeting_notes.yaml +51 -0
- contextsynapse/config/schemas/news_article.yaml +61 -0
- contextsynapse/config/schemas/real_estate.yaml +49 -0
- contextsynapse/config/schemas/requirements_doc.yaml +48 -0
- contextsynapse/config/schemas/research_paper.yaml +56 -0
- contextsynapse/config/schemas/resume.yaml +49 -0
- contextsynapse/config/schemas/retail.yaml +42 -0
- contextsynapse/config/schemas/rule_meta.yaml +173 -0
- contextsynapse/config/schemas/rules.yaml +72 -0
- contextsynapse/config/schemas/screener_meta.yaml +236 -0
- contextsynapse/config/schemas/sdlc.yaml +103 -0
- contextsynapse/config/schemas/session_graph.yaml +172 -0
- contextsynapse/config/schemas/support_ticket.yaml +48 -0
- contextsynapse/config/schemas/web.yaml +64 -0
- contextsynapse/config/sensors/earnings_calendar.yaml +52 -0
- contextsynapse/config/sensors/global_risk.yaml +53 -0
- contextsynapse/config/sensors/government_policy.yaml +54 -0
- contextsynapse/config/sensors/supply_chain.yaml +50 -0
- contextsynapse/config/sensors/templates/behavioral.yaml +56 -0
- contextsynapse/config/sensors/templates/equity_sentiment.yaml +48 -0
- contextsynapse/config/sensors/templates/global_macro.yaml +47 -0
- contextsynapse/config/sensors/templates/market_data.yaml +118 -0
- contextsynapse/config/sensors/templates/sector_technical.yaml +47 -0
- contextsynapse/config/sensors/weather.yaml +52 -0
- contextsynapse/config/storage_strategy.py +202 -0
- contextsynapse/config/templates/competitive_intel.yaml +39 -0
- contextsynapse/config/templates/geopolitical_risk.yaml +46 -0
- contextsynapse/config/templates/market_analysis.yaml +58 -0
- contextsynapse/connectors/__init__.py +15 -0
- contextsynapse/connectors/api_router.py +121 -0
- contextsynapse/connectors/base.py +357 -0
- contextsynapse/connectors/financial/audio.py +170 -0
- contextsynapse/connectors/financial/youtube.py +272 -0
- contextsynapse/connectors/github.py +164 -0
- contextsynapse/connectors/jira.py +120 -0
- contextsynapse/connectors/kafka_connector.py +124 -0
- contextsynapse/connectors/pipeline_connector.py +747 -0
- contextsynapse/connectors/registry.py +116 -0
- contextsynapse/connectors/scheduler.py +87 -0
- contextsynapse/connectors/universal.py +360 -0
- contextsynapse/context/__init__.py +96 -0
- contextsynapse/context/__main__.py +4 -0
- contextsynapse/context/acl.py +125 -0
- contextsynapse/context/agent_card.py +201 -0
- contextsynapse/context/agent_memory.py +814 -0
- contextsynapse/context/agents.py +472 -0
- contextsynapse/context/agents_redis.py +391 -0
- contextsynapse/context/assembled.py +204 -0
- contextsynapse/context/attribution.py +113 -0
- contextsynapse/context/blob.py +184 -0
- contextsynapse/context/boundaries.py +187 -0
- contextsynapse/context/boundary.py +507 -0
- contextsynapse/context/bundle.py +155 -0
- contextsynapse/context/cli.py +519 -0
- contextsynapse/context/collaboration.py +165 -0
- contextsynapse/context/compiler.py +604 -0
- contextsynapse/context/composite.py +536 -0
- contextsynapse/context/compression.py +147 -0
- contextsynapse/context/context_manager.py +888 -0
- contextsynapse/context/context_schema.py +90 -0
- contextsynapse/context/context_schema.yaml +105 -0
- contextsynapse/context/context_units.py +891 -0
- contextsynapse/context/conversation.py +488 -0
- contextsynapse/context/dedup.py +207 -0
- contextsynapse/context/document_processor.py +698 -0
- contextsynapse/context/embedding_hooks.py +338 -0
- contextsynapse/context/execution_schema.py +274 -0
- contextsynapse/context/fan_out.py +138 -0
- contextsynapse/context/frozen.py +80 -0
- contextsynapse/context/gravity.py +227 -0
- contextsynapse/context/hub.py +1401 -0
- contextsynapse/context/ingest.py +391 -0
- contextsynapse/context/injection.py +226 -0
- contextsynapse/context/interaction_graphifier.py +562 -0
- contextsynapse/context/layers.py +574 -0
- contextsynapse/context/projection.py +1195 -0
- contextsynapse/context/promotion.py +634 -0
- contextsynapse/context/proof.py +131 -0
- contextsynapse/context/propagation.py +577 -0
- contextsynapse/context/pubsub.py +135 -0
- contextsynapse/context/quality.py +1054 -0
- contextsynapse/context/quality_tracker.py +270 -0
- contextsynapse/context/scoping.py +370 -0
- contextsynapse/context/seed.py +992 -0
- contextsynapse/context/session.py +1142 -0
- contextsynapse/context/session_graph.py +593 -0
- contextsynapse/context/session_memory.py +309 -0
- contextsynapse/context/session_resolver.py +111 -0
- contextsynapse/context/skill_learning.py +237 -0
- contextsynapse/context/smart_budget.py +129 -0
- contextsynapse/context/source_policy.py +242 -0
- contextsynapse/context/store_factory.py +53 -0
- contextsynapse/context/sync.py +648 -0
- contextsynapse/context/token.py +236 -0
- contextsynapse/context/vector_integration.py +297 -0
- contextsynapse/context/webhooks.py +194 -0
- contextsynapse/context/working_memory.py +224 -0
- contextsynapse/core/__init__.py +18 -0
- contextsynapse/core/archiver.py +237 -0
- contextsynapse/core/change_stream.py +282 -0
- contextsynapse/core/checkpoint.py +310 -0
- contextsynapse/core/cloud_storage.py +387 -0
- contextsynapse/core/context_state.py +651 -0
- contextsynapse/core/cron.py +324 -0
- contextsynapse/core/datasource.py +643 -0
- contextsynapse/core/db.py +289 -0
- contextsynapse/core/federation.py +283 -0
- contextsynapse/core/graph_coordinator.py +201 -0
- contextsynapse/core/graph_intelligence.py +388 -0
- contextsynapse/core/graph_structures.py +106 -0
- contextsynapse/core/graph_sync.py +157 -0
- contextsynapse/core/hybrid_graph_storage.py +2998 -0
- contextsynapse/core/lifecycle.py +691 -0
- contextsynapse/core/multiworker.py +378 -0
- contextsynapse/core/pruning.py +312 -0
- contextsynapse/core/redis_registry.py +524 -0
- contextsynapse/core/registry.py +1043 -0
- contextsynapse/core/registry_factory.py +51 -0
- contextsynapse/core/registry_metadata.py +294 -0
- contextsynapse/core/replay.py +354 -0
- contextsynapse/core/replication.py +339 -0
- contextsynapse/core/time_travel.py +260 -0
- contextsynapse/core/write_behind.py +152 -0
- contextsynapse/core/write_context.py +39 -0
- contextsynapse/db/__init__.py +1 -0
- contextsynapse/db/postgres.py +267 -0
- contextsynapse/db/profile.py +236 -0
- contextsynapse/db/rules.py +328 -0
- contextsynapse/demo/__init__.py +0 -0
- contextsynapse/demo/report.py +147 -0
- contextsynapse/demo/runner.py +520 -0
- contextsynapse/demo/scenario.py +165 -0
- contextsynapse/demo/session_demo.py +280 -0
- contextsynapse/demo/session_report.py +143 -0
- contextsynapse/demo/universal_runner.py +257 -0
- contextsynapse/engine/__init__.py +15 -0
- contextsynapse/engine/core.py +555 -0
- contextsynapse/extraction/__init__.py +69 -0
- contextsynapse/extraction/cli.py +274 -0
- contextsynapse/extraction/config.py +107 -0
- contextsynapse/extraction/examples/example_usage.py +118 -0
- contextsynapse/extraction/examples/sample_manifest.json +53 -0
- contextsynapse/extraction/examples/sample_normalized.json +185 -0
- contextsynapse/extraction/extract_engine.py +440 -0
- contextsynapse/extraction/extractors/__init__.py +37 -0
- contextsynapse/extraction/extractors/chat_export_extractor.py +189 -0
- contextsynapse/extraction/extractors/csv_extractor.py +83 -0
- contextsynapse/extraction/extractors/docx_extractor.py +129 -0
- contextsynapse/extraction/extractors/excel_extractor.py +152 -0
- contextsynapse/extraction/extractors/html_extractor.py +146 -0
- contextsynapse/extraction/extractors/pdf_extractor.py +635 -0
- contextsynapse/extraction/extractors/pdf_extractor_layoutparser.py +408 -0
- contextsynapse/extraction/extractors/register_extractors.py +63 -0
- contextsynapse/extraction/extractors/text_extractor.py +65 -0
- contextsynapse/extraction/extractors/txt_extractor.py +120 -0
- contextsynapse/extraction/extractors/website_extractor.py +246 -0
- contextsynapse/extraction/fact_extractor.py +236 -0
- contextsynapse/extraction/file_manager.py +353 -0
- contextsynapse/extraction/hierarchy.py +239 -0
- contextsynapse/extraction/id_generator.py +450 -0
- contextsynapse/extraction/layout_detector.py +409 -0
- contextsynapse/extraction/llm_entity_extractor.py +301 -0
- contextsynapse/extraction/normalized_store.py +480 -0
- contextsynapse/extraction/normalizer.py +594 -0
- contextsynapse/extraction/parquet_writer.py +598 -0
- contextsynapse/extraction/post_processors/__init__.py +40 -0
- contextsynapse/extraction/post_processors/column_reorganizer.py +507 -0
- contextsynapse/extraction/ray_normalization.py +233 -0
- contextsynapse/extraction/ray_runner.py +207 -0
- contextsynapse/extraction/redis_schema_registry.py +222 -0
- contextsynapse/extraction/registry.py +204 -0
- contextsynapse/extraction/schema_loader.py +663 -0
- contextsynapse/extraction/schema_registry.py +230 -0
- contextsynapse/extraction/semantic_normalizer.py +245 -0
- contextsynapse/extraction/version_manager.py +314 -0
- contextsynapse/gateway/__init__.py +24 -0
- contextsynapse/gateway/action_emitter.py +212 -0
- contextsynapse/gateway/cost_tracker.py +508 -0
- contextsynapse/gateway/gateway.py +377 -0
- contextsynapse/gateway/policy.py +303 -0
- contextsynapse/governance/__init__.py +13 -0
- contextsynapse/governance/layer.py +513 -0
- contextsynapse/ingestion/__init__.py +15 -0
- contextsynapse/ingestion/amplifier.py +288 -0
- contextsynapse/ingestion/async_ingest.py +463 -0
- contextsynapse/ingestion/chunker.py +303 -0
- contextsynapse/ingestion/chunking.py +58 -0
- contextsynapse/ingestion/cleaner.py +190 -0
- contextsynapse/ingestion/cleanse.py +252 -0
- contextsynapse/ingestion/connectors/__init__.py +38 -0
- contextsynapse/ingestion/connectors/base.py +300 -0
- contextsynapse/ingestion/connectors/chatgpt_memory.py +247 -0
- contextsynapse/ingestion/connectors/claude_memory.py +274 -0
- contextsynapse/ingestion/connectors/sap_ingestor.py +208 -0
- contextsynapse/ingestion/content_detector.py +80 -0
- contextsynapse/ingestion/content_monitor.py +502 -0
- contextsynapse/ingestion/dedup.py +106 -0
- contextsynapse/ingestion/feed.py +640 -0
- contextsynapse/ingestion/filters.py +461 -0
- contextsynapse/ingestion/graph_builder.py +705 -0
- contextsynapse/ingestion/input_classifier.py +307 -0
- contextsynapse/ingestion/job_manager.py +571 -0
- contextsynapse/ingestion/llm_extractor.py +610 -0
- contextsynapse/ingestion/orchestrator.py +116 -0
- contextsynapse/ingestion/parsers/__init__.py +19 -0
- contextsynapse/ingestion/parsers/graph_parser_registry.py +103 -0
- contextsynapse/ingestion/parsers/graphml_parser.py +141 -0
- contextsynapse/ingestion/parsers/jsonld_parser.py +171 -0
- contextsynapse/ingestion/parsers/rdf_parser.py +150 -0
- contextsynapse/ingestion/pipeline_context.py +228 -0
- contextsynapse/ingestion/pipeline_store.py +1195 -0
- contextsynapse/ingestion/queue.py +133 -0
- contextsynapse/ingestion/reprocess.py +179 -0
- contextsynapse/ingestion/scenario_router.py +224 -0
- contextsynapse/ingestion/schema_extractor.py +759 -0
- contextsynapse/ingestion/schema_validator.py +274 -0
- contextsynapse/ingestion/sdlc_ingest.py +87 -0
- contextsynapse/ingestion/smart_ingest.py +2451 -0
- contextsynapse/ingestion/source_connector.py +171 -0
- contextsynapse/ingestion/stage_executor.py +2095 -0
- contextsynapse/ingestion/stages/__init__.py +9 -0
- contextsynapse/ingestion/stages/tabular_to_graph.py +289 -0
- contextsynapse/ingestion/strategies.py +386 -0
- contextsynapse/ingestion/universal/__init__.py +4 -0
- contextsynapse/ingestion/universal/_operator_registry.py +59 -0
- contextsynapse/ingestion/universal/ingest_content.py +32 -0
- contextsynapse/ingestion/universal/operators/__init__.py +1 -0
- contextsynapse/ingestion/universal/operators/base.py +19 -0
- contextsynapse/ingestion/universal/operators/build_edges.py +75 -0
- contextsynapse/ingestion/universal/operators/cluster_topics.py +397 -0
- contextsynapse/ingestion/universal/operators/deduplicate.py +45 -0
- contextsynapse/ingestion/universal/operators/detect_signals.py +90 -0
- contextsynapse/ingestion/universal/operators/embed.py +80 -0
- contextsynapse/ingestion/universal/operators/extract_entities.py +253 -0
- contextsynapse/ingestion/universal/operators/extract_preferences.py +117 -0
- contextsynapse/ingestion/universal/operators/filter_content.py +69 -0
- contextsynapse/ingestion/universal/operators/index_bm25.py +52 -0
- contextsynapse/ingestion/universal/operators/infer_domains.py +69 -0
- contextsynapse/ingestion/universal/operators/link_cross_reference.py +210 -0
- contextsynapse/ingestion/universal/operators/parse_records.py +66 -0
- contextsynapse/ingestion/universal/operators/resolve_entities.py +261 -0
- contextsynapse/ingestion/universal/operators/scanners/__init__.py +16 -0
- contextsynapse/ingestion/universal/operators/scanners/edges.py +173 -0
- contextsynapse/ingestion/universal/operators/scanners/git.py +187 -0
- contextsynapse/ingestion/universal/operators/scanners/github.py +135 -0
- contextsynapse/ingestion/universal/operators/scanners/github_api.py +509 -0
- contextsynapse/ingestion/universal/operators/scanners/helpers.py +67 -0
- contextsynapse/ingestion/universal/operators/scanners/jira.py +65 -0
- contextsynapse/ingestion/universal/operators/scanners/llm_enrichment.py +82 -0
- contextsynapse/ingestion/universal/operators/scanners/repo.py +149 -0
- contextsynapse/ingestion/universal/operators/sdlc_scan.py +213 -0
- contextsynapse/ingestion/universal/operators/spec_scanner.py +149 -0
- contextsynapse/ingestion/universal/operators/store_documents.py +72 -0
- contextsynapse/ingestion/universal/operators/synthesize_cu.py +180 -0
- contextsynapse/ingestion/universal/operators/validate_gate.py +65 -0
- contextsynapse/ingestion/universal/parsers/__init__.py +5 -0
- contextsynapse/ingestion/universal/parsers/record_parser.py +203 -0
- contextsynapse/ingestion/universal/parsers/turn_parser.py +329 -0
- contextsynapse/ingestion/universal/shared_signals.py +161 -0
- contextsynapse/ingestion/universal/stage_executor.py +219 -0
- contextsynapse/ingestion/web_crawler.py +265 -0
- contextsynapse/intelligence/__init__.py +53 -0
- contextsynapse/intelligence/alerts.py +198 -0
- contextsynapse/intelligence/collector.py +655 -0
- contextsynapse/intelligence/comparison.py +198 -0
- contextsynapse/intelligence/config.py +56 -0
- contextsynapse/intelligence/conflict_detector.py +182 -0
- contextsynapse/intelligence/context_gaps.py +192 -0
- contextsynapse/intelligence/context_radar.py +135 -0
- contextsynapse/intelligence/correlation_engine.py +416 -0
- contextsynapse/intelligence/correlation_template.py +120 -0
- contextsynapse/intelligence/dedup.py +296 -0
- contextsynapse/intelligence/event_bus.py +91 -0
- contextsynapse/intelligence/feedback_loop.py +101 -0
- contextsynapse/intelligence/freshness.py +307 -0
- contextsynapse/intelligence/fundamental_feed.py +551 -0
- contextsynapse/intelligence/geo_data.py +170 -0
- contextsynapse/intelligence/geo_reference.py +112 -0
- contextsynapse/intelligence/graph_fusion.py +465 -0
- contextsynapse/intelligence/heuristics.py +176 -0
- contextsynapse/intelligence/impact_tracker.py +336 -0
- contextsynapse/intelligence/macro_indicators.py +319 -0
- contextsynapse/intelligence/ocr.py +104 -0
- contextsynapse/intelligence/persistence.py +267 -0
- contextsynapse/intelligence/pipeline_health.py +130 -0
- contextsynapse/intelligence/price_feed.py +221 -0
- contextsynapse/intelligence/rate_limiter.py +130 -0
- contextsynapse/intelligence/reactive.py +411 -0
- contextsynapse/intelligence/runtime_context.py +390 -0
- contextsynapse/intelligence/sentiment_decay.py +426 -0
- contextsynapse/intelligence/session.py +277 -0
- contextsynapse/intelligence/signal_hierarchy.py +438 -0
- contextsynapse/intelligence/signals.py +185 -0
- contextsynapse/intelligence/source_watcher.py +132 -0
- contextsynapse/intelligence/table_enricher.py +256 -0
- contextsynapse/intelligence/tagger.py +319 -0
- contextsynapse/intelligence/timeseries.py +209 -0
- contextsynapse/intelligence/tracker.py +318 -0
- contextsynapse/intelligence/watchdog.py +392 -0
- contextsynapse/intelligence/watchdog_manager.py +263 -0
- contextsynapse/intelligence/webhook_receiver.py +165 -0
- contextsynapse/intelligence/weight_learner.py +234 -0
- contextsynapse/llm/__init__.py +8 -0
- contextsynapse/llm/client.py +348 -0
- contextsynapse/llm/rate_limiter.py +166 -0
- contextsynapse/logging_config.py +2 -0
- contextsynapse/marketplace/__init__.py +7 -0
- contextsynapse/marketplace/registry.py +173 -0
- contextsynapse/mcp/__init__.py +11 -0
- contextsynapse/mcp/__main__.py +4 -0
- contextsynapse/mcp/auth_middleware.py +212 -0
- contextsynapse/mcp/connection_pool.py +128 -0
- contextsynapse/mcp/server.py +986 -0
- contextsynapse/mcp/session_context.py +67 -0
- contextsynapse/memory/__init__.py +34 -0
- contextsynapse/memory/engine.py +475 -0
- contextsynapse/memory/temporal.py +327 -0
- contextsynapse/metadata/__init__.py +12 -0
- contextsynapse/metadata/metadata_db.py +272 -0
- contextsynapse/metadata/metadata_query_tool.py +300 -0
- contextsynapse/metadata/metadata_tracker.py +425 -0
- contextsynapse/models/__init__.py +27 -0
- contextsynapse/models/embedding_service.py +360 -0
- contextsynapse/models/model_registry.py +228 -0
- contextsynapse/multiworker.py +2 -0
- contextsynapse/pipelines/__init__.py +1 -0
- contextsynapse/pipelines/executor.py +851 -0
- contextsynapse/pipelines/filters.py +189 -0
- contextsynapse/pipelines/models.py +176 -0
- contextsynapse/pipelines/scheduler.py +268 -0
- contextsynapse/playground/__init__.py +1 -0
- contextsynapse/playground/templates.py +85 -0
- contextsynapse/plugins/__init__.py +43 -0
- contextsynapse/plugins/api.py +66 -0
- contextsynapse/plugins/base.py +350 -0
- contextsynapse/plugins/domain_interface.py +508 -0
- contextsynapse/plugins/domain_loader.py +241 -0
- contextsynapse/plugins/loader.py +148 -0
- contextsynapse/plugins/registry.py +265 -0
- contextsynapse/project/__init__.py +11 -0
- contextsynapse/project/code_context.py +807 -0
- contextsynapse/project/connectors/__init__.py +4 -0
- contextsynapse/project/connectors/github_connector.py +113 -0
- contextsynapse/project/connectors/jira_connector.py +160 -0
- contextsynapse/project/graph.py +1023 -0
- contextsynapse/project/project_context.py +545 -0
- contextsynapse/project/scanner_registry.py +61 -0
- contextsynapse/project/schema_manager.py +322 -0
- contextsynapse/project/schemas/knowledge.yaml +59 -0
- contextsynapse/project/schemas/sdlc.yaml +369 -0
- contextsynapse/project/sdlc_schema.py +172 -0
- contextsynapse/project/spec_parser.py +85 -0
- contextsynapse/project/task_lock.py +181 -0
- contextsynapse/project/task_stream.py +203 -0
- contextsynapse/project/team_tools.py +537 -0
- contextsynapse/py.typed +0 -0
- contextsynapse/realtime/__init__.py +6 -0
- contextsynapse/realtime/collaboration.py +147 -0
- contextsynapse/rules/__init__.py +10 -0
- contextsynapse/rules/engine.py +116 -0
- contextsynapse/rules/evaluators/__init__.py +6 -0
- contextsynapse/rules/evaluators/aggregate.py +205 -0
- contextsynapse/rules/evaluators/custom.py +170 -0
- contextsynapse/rules/evaluators/pre_action.py +125 -0
- contextsynapse/rules/evaluators/temporal.py +129 -0
- contextsynapse/rules/loader.py +52 -0
- contextsynapse/rules/models.py +124 -0
- contextsynapse/rules/universal.py +393 -0
- contextsynapse/scheduler.py +229 -0
- contextsynapse/schema/__init__.py +42 -0
- contextsynapse/schema/compiler.py +252 -0
- contextsynapse/schema/composer.py +158 -0
- contextsynapse/schema/dedup.py +73 -0
- contextsynapse/schema/derivation.py +172 -0
- contextsynapse/schema/sdl.py +326 -0
- contextsynapse/schema/validation_gate.py +124 -0
- contextsynapse/sdk/__init__.py +63 -0
- contextsynapse/sdk/agent.py +214 -0
- contextsynapse/sdk/client.py +269 -0
- contextsynapse/sdk/exceptions.py +23 -0
- contextsynapse/sdk/models.py +78 -0
- contextsynapse/sdk/session.py +408 -0
- contextsynapse/sdk/tracked.py +218 -0
- contextsynapse/sdk/worker.py +266 -0
- contextsynapse/sdk/workspace_client.py +98 -0
- contextsynapse/sdk/ws.py +41 -0
- contextsynapse/search/__init__.py +20 -0
- contextsynapse/search/embedding_cache.py +140 -0
- contextsynapse/search/enhanced_search.py +152 -0
- contextsynapse/search/fulltext.py +130 -0
- contextsynapse/search/graph_search.py +634 -0
- contextsynapse/search/lmdb_index.py +1218 -0
- contextsynapse/search/nl_to_aiql.py +199 -0
- contextsynapse/search/rag.py +1346 -0
- contextsynapse/search/rag_cache.py +167 -0
- contextsynapse/search/reasoning_chain.py +231 -0
- contextsynapse/search/redis_search.py +207 -0
- contextsynapse/search/retrieval_quality.py +314 -0
- contextsynapse/search/semantic_query.py +455 -0
- contextsynapse/search/text_resolver.py +159 -0
- contextsynapse/search/whoosh_search.py +373 -0
- contextsynapse/security/__init__.py +17 -0
- contextsynapse/security/audit.py +297 -0
- contextsynapse/security/audit_logger.py +97 -0
- contextsynapse/security/audit_trail.py +270 -0
- contextsynapse/security/auth_unified.py +284 -0
- contextsynapse/security/auto_tagger.py +230 -0
- contextsynapse/security/data_security.py +218 -0
- contextsynapse/security/encryption.py +207 -0
- contextsynapse/security/identity.py +447 -0
- contextsynapse/security/jwt_identity.py +59 -0
- contextsynapse/security/middleware.py +336 -0
- contextsynapse/security/pii.py +233 -0
- contextsynapse/security/rbac.py +96 -0
- contextsynapse/security/rls.py +205 -0
- contextsynapse/security/sanitize.py +233 -0
- contextsynapse/security/scoped_encryption.py +131 -0
- contextsynapse/security/tenant.py +401 -0
- contextsynapse/shield/__init__.py +21 -0
- contextsynapse/shield/anomaly.py +56 -0
- contextsynapse/shield/permissions.py +99 -0
- contextsynapse/shield/profile.py +103 -0
- contextsynapse/shield/shield.py +108 -0
- contextsynapse/shield/trust_engine.py +69 -0
- contextsynapse/skills/__init__.py +14 -0
- contextsynapse/skills/engine.py +233 -0
- contextsynapse/skills/loader.py +59 -0
- contextsynapse/skills/models.py +139 -0
- contextsynapse/storage/__init__.py +7 -0
- contextsynapse/storage/cache.py +208 -0
- contextsynapse/storage/columnar_store_v2.py +178 -0
- contextsynapse/storage/csr_graph_storage.py +587 -0
- contextsynapse/storage/csr_store.py +388 -0
- contextsynapse/storage/document_store.py +491 -0
- contextsynapse/storage/hnsw_index.py +294 -0
- contextsynapse/storage/hybrid_store_v2.py +278 -0
- contextsynapse/storage/lmdb_graph_storage.py +366 -0
- contextsynapse/storage/migration.py +279 -0
- contextsynapse/storage/namespace_store.py +346 -0
- contextsynapse/storage/pure_graph_storage.py +185 -0
- contextsynapse/storage/redis_graph_adapter.py +186 -0
- contextsynapse/storage/redis_graph_storage.py +375 -0
- contextsynapse/storage/router/__init__.py +29 -0
- contextsynapse/storage/router/duckdb_store.py +398 -0
- contextsynapse/storage/router/factory.py +83 -0
- contextsynapse/storage/router/interface.py +125 -0
- contextsynapse/storage/router/postgres_store.py +339 -0
- contextsynapse/storage/router/resolver.py +192 -0
- contextsynapse/storage/router/router.py +300 -0
- contextsynapse/storage/single_file_storage.py +391 -0
- contextsynapse/storage/storage_manager.py +214 -0
- contextsynapse/storage/storage_query_optimizer.py +314 -0
- contextsynapse/storage/wal.py +359 -0
- contextsynapse/temporal/__init__.py +0 -0
- contextsynapse/temporal/storage.py +106 -0
- contextsynapse/tests/__init__.py +11 -0
- contextsynapse/tests/regression/__init__.py +1 -0
- contextsynapse/tests/regression/add_metadata.py +395 -0
- contextsynapse/tests/regression/add_skip_flags.py +85 -0
- contextsynapse/tests/regression/cases/test_basic_create_node.yaml +20 -0
- contextsynapse/tests/regression/cases/test_basic_select.yaml +21 -0
- contextsynapse/tests/regression/cases/test_blockchain_audit_trail.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_audit_trail_filtered.yaml +39 -0
- contextsynapse/tests/regression/cases/test_blockchain_blocks_range.yaml +39 -0
- contextsynapse/tests/regression/cases/test_blockchain_get_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_latest_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_length.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_merkle_root.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_verify.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_verify_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_create_edge.yaml +28 -0
- contextsynapse/tests/regression/cases/test_delete_edge_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_delete_node_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_evaluation_mrr.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_ndcg.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_precision.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_recall.yaml +40 -0
- contextsynapse/tests/regression/cases/test_group_by_basic.yaml +56 -0
- contextsynapse/tests/regression/cases/test_match_node_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_match_node_with_order_limit.yaml +31 -0
- contextsynapse/tests/regression/cases/test_namespace_switch.yaml +18 -0
- contextsynapse/tests/regression/cases/test_pipeline_cascaded.yaml +61 -0
- contextsynapse/tests/regression/cases/test_pipeline_fixed_size_chunking.yaml +48 -0
- contextsynapse/tests/regression/cases/test_pipeline_gpt4_chunking.yaml +54 -0
- contextsynapse/tests/regression/cases/test_pipeline_hybrid_indexing.yaml +57 -0
- contextsynapse/tests/regression/cases/test_pipeline_large_embedding.yaml +52 -0
- contextsynapse/tests/regression/cases/test_pipeline_paragraph_chunking.yaml +49 -0
- contextsynapse/tests/regression/cases/test_pipeline_recursive_chunking.yaml +47 -0
- contextsynapse/tests/regression/cases/test_pipeline_run_pipeline.yaml +56 -0
- contextsynapse/tests/regression/cases/test_pipeline_section_based_chunking.yaml +53 -0
- contextsynapse/tests/regression/cases/test_pipeline_semantic_chunking.yaml +51 -0
- contextsynapse/tests/regression/cases/test_pipeline_with_entity_extraction.yaml +57 -0
- contextsynapse/tests/regression/cases/test_pipeline_with_relationships.yaml +62 -0
- contextsynapse/tests/regression/cases/test_rag_evaluation.yaml +38 -0
- contextsynapse/tests/regression/cases/test_rag_graph_bfs_with_eval.yaml +48 -0
- contextsynapse/tests/regression/cases/test_rag_graph_centrality.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_centrality_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_community.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_community_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_dfs_with_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_multi_hop_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_path.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_basic.yaml +38 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_bfs.yaml +52 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_dfs.yaml +41 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_multi_hop.yaml +43 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_shortest_path.yaml +41 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_weighted.yaml +43 -0
- contextsynapse/tests/regression/cases/test_rag_graph_shortest_path_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_traversal.yaml +30 -0
- contextsynapse/tests/regression/cases/test_rag_graph_weighted_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_hybrid_search.yaml +33 -0
- contextsynapse/tests/regression/cases/test_rag_hybrid_with_prompt_embedding.yaml +44 -0
- contextsynapse/tests/regression/cases/test_rag_keyword_search.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_multi_hop_graph.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_path_based.yaml +34 -0
- contextsynapse/tests/regression/cases/test_rag_profile_based.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_vector_search.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_weighted_graph.yaml +34 -0
- contextsynapse/tests/regression/cases/test_rag_weighted_hybrid.yaml +31 -0
- contextsynapse/tests/regression/cases/test_semantic_boundary_chunking.yaml +57 -0
- contextsynapse/tests/regression/cases/test_semantic_boundary_detection.yaml +33 -0
- contextsynapse/tests/regression/cases/test_semantic_hash_function.yaml +30 -0
- contextsynapse/tests/regression/cases/test_semantic_hash_with_model.yaml +31 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_fixed_size.yaml +44 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_recursive.yaml +46 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_section.yaml +46 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_semantic.yaml +44 -0
- contextsynapse/tests/regression/cases/test_time_travel_as_of.yaml +35 -0
- contextsynapse/tests/regression/cases/test_time_travel_at_commit.yaml +33 -0
- contextsynapse/tests/regression/cases/test_time_travel_at_timestamp.yaml +33 -0
- contextsynapse/tests/regression/cases/test_time_travel_for_system_time_all.yaml +37 -0
- contextsynapse/tests/regression/cases/test_time_travel_versions_between.yaml +39 -0
- contextsynapse/tests/regression/cases/test_traverse_basic.yaml +31 -0
- contextsynapse/tests/regression/cases/test_traverse_max_depth.yaml +34 -0
- contextsynapse/tests/regression/cases/test_update_edge_basic.yaml +29 -0
- contextsynapse/tests/regression/evaluation_metrics.py +227 -0
- contextsynapse/tests/regression/regression_runner.py +1081 -0
- contextsynapse/tests/regression/setup_regression_data.py +212 -0
- contextsynapse/tests/regression/test_basic_only.py +84 -0
- contextsynapse/tests/regression/test_namespace_creation.py +29 -0
- contextsynapse/tests/regression/test_single_fix.py +61 -0
- contextsynapse/tests/regression/test_single_query.py +20 -0
- contextsynapse/tests/regression/validate_test_syntax.py +179 -0
- contextsynapse/tests/test_advanced_features.py +347 -0
- contextsynapse/tests/test_aiql_summary.py +283 -0
- contextsynapse/tests/test_all_aiql_queries.py +948 -0
- contextsynapse/tests/test_all_create_queries.py +213 -0
- contextsynapse/tests/test_api_vs_native.py +280 -0
- contextsynapse/tests/test_chunking_dedup.py +199 -0
- contextsynapse/tests/test_connectors.py +171 -0
- contextsynapse/tests/test_edges_and_match.py +202 -0
- contextsynapse/tests/test_extraction_pipeline_integration.py +581 -0
- contextsynapse/tests/test_name_property.py +224 -0
- contextsynapse/tests/test_node_edge_metadata.py +250 -0
- contextsynapse/tests/test_playground_conn.py +114 -0
- contextsynapse/tests/test_query_cache.py +213 -0
- contextsynapse/tests/test_source_policy_auth.py +348 -0
- contextsynapse/tests/test_temporal_and_domain.py +224 -0
- contextsynapse/tests/test_time_travel_e2e.py +571 -0
- contextsynapse/tests/test_traversal_and_aggregation.py +275 -0
- contextsynapse/tools/__init__.py +36 -0
- contextsynapse/tools/a2a_tools.py +106 -0
- contextsynapse/tools/cognition_tools.py +118 -0
- contextsynapse/tools/context.py +905 -0
- contextsynapse/tools/formatting.py +92 -0
- contextsynapse/tools/gateway_tools.py +186 -0
- contextsynapse/tools/graph.py +1700 -0
- contextsynapse/tools/intelligence.py +235 -0
- contextsynapse/tools/memory.py +118 -0
- contextsynapse/tools/registry.py +373 -0
- contextsynapse/tools/shield_tools.py +159 -0
- contextsynapse/tools/tasks.py +341 -0
- contextsynapse/tools/workspace_tools.py +124 -0
- contextsynapse/utils/__init__.py +15 -0
- contextsynapse/utils/config_loader.py +198 -0
- contextsynapse/utils/env_loader.py +156 -0
- contextsynapse/utils/file_utils.py +288 -0
- contextsynapse/utils/logging_optimizer.py +119 -0
- contextsynapse/vector/__init__.py +30 -0
- contextsynapse/vector/chroma_vector_store.py +152 -0
- contextsynapse/vector/custom_vector_store.py +537 -0
- contextsynapse/vector/faiss_vector_store.py +296 -0
- contextsynapse/vector/qdrant_vector_store.py +163 -0
- contextsynapse/vector/vector_db.py +953 -0
- contextsynapse/vector/vector_db_manager.py +197 -0
- contextsynapse/workspace/__init__.py +27 -0
- contextsynapse/workspace/base.py +94 -0
- contextsynapse/workspace/git.py +228 -0
- contextsynapse/workspace/github.py +78 -0
- contextsynapse/workspace/local.py +73 -0
- contextsynapse/workspace/tools.py +222 -0
- contextsynapse-1.0.0.dist-info/METADATA +847 -0
- contextsynapse-1.0.0.dist-info/RECORD +776 -0
- contextsynapse-1.0.0.dist-info/WHEEL +5 -0
- contextsynapse-1.0.0.dist-info/entry_points.txt +4 -0
- contextsynapse-1.0.0.dist-info/licenses/LICENSE +200 -0
- contextsynapse-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Worker Router
|
|
3
|
+
====================
|
|
4
|
+
REST API for remote agents to poll tasks, claim work, report results,
|
|
5
|
+
and interact with the shared graph — all authenticated via agent_id:secret.
|
|
6
|
+
|
|
7
|
+
Mount on the main FastAPI app::
|
|
8
|
+
|
|
9
|
+
from contextsynapse.api.agent_worker_router import create_agent_worker_router
|
|
10
|
+
app.include_router(create_agent_worker_router(graph_registry))
|
|
11
|
+
|
|
12
|
+
Endpoints:
|
|
13
|
+
GET /agent/sessions — List agent's sessions
|
|
14
|
+
GET /agent/sessions/{sid}/tasks/mine — Poll assigned tasks
|
|
15
|
+
GET /agent/sessions/{sid}/tasks/available — Poll unassigned tasks
|
|
16
|
+
POST /agent/sessions/{sid}/tasks/{tid}/claim — Claim a task
|
|
17
|
+
POST /agent/sessions/{sid}/tasks/{tid}/complete — Report completion
|
|
18
|
+
POST /agent/sessions/{sid}/tasks/{tid}/handoff — Hand off to another agent
|
|
19
|
+
POST /agent/sessions/{sid}/heartbeat — Status heartbeat
|
|
20
|
+
GET /agent/sessions/{sid}/workspace/config — Get workspace config
|
|
21
|
+
POST /agent/sessions/{sid}/workspace/artifacts — Upload files (no-git agents)
|
|
22
|
+
POST /agent/sessions/{sid}/workspace/push-event — Notify of git push
|
|
23
|
+
POST /agent/sessions/{sid}/graph/nodes — Add knowledge node
|
|
24
|
+
POST /agent/sessions/{sid}/graph/query — Execute AIQL query
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import logging
|
|
31
|
+
from datetime import datetime, timezone
|
|
32
|
+
from typing import Any, Dict, List, Optional
|
|
33
|
+
|
|
34
|
+
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
|
35
|
+
from pydantic import BaseModel, Field
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ── Platform → Tier Mapping ──────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
_PLATFORM_TIER: dict[str, str] = {
|
|
43
|
+
"embedded": "instant",
|
|
44
|
+
"mobile": "fast",
|
|
45
|
+
"browser": "fast",
|
|
46
|
+
"desktop": "standard",
|
|
47
|
+
"app": "standard",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _tier_for_platform(platform: str | None, explicit_tier: str | None) -> str:
|
|
52
|
+
"""Resolve projection tier: explicit param wins; otherwise use platform default."""
|
|
53
|
+
if explicit_tier is not None:
|
|
54
|
+
return explicit_tier
|
|
55
|
+
return _PLATFORM_TIER.get(platform or "", "standard")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
_TIER_BUDGETS: dict[str, dict] = {
|
|
59
|
+
"instant": {"token_budget": 500, "time_budget_ms": 50},
|
|
60
|
+
"fast": {"token_budget": 1500, "time_budget_ms": 200},
|
|
61
|
+
"standard":{"token_budget": 3000, "time_budget_ms": 1000},
|
|
62
|
+
"deep": {"token_budget": 8000, "time_budget_ms": 5000},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _build_platform_hints(platform: str | None, resolved_tier: str, tier_was_explicit: bool) -> dict:
|
|
67
|
+
"""Build the platform_hints block for orient_agent responses."""
|
|
68
|
+
budgets = _TIER_BUDGETS.get(resolved_tier, _TIER_BUDGETS["standard"])
|
|
69
|
+
if tier_was_explicit:
|
|
70
|
+
note = "Tier explicitly set by caller."
|
|
71
|
+
else:
|
|
72
|
+
note = f"Tier auto-selected for {platform or 'unknown'} platform. Pass ?tier=deep to override."
|
|
73
|
+
return {
|
|
74
|
+
"platform": platform or "unknown",
|
|
75
|
+
"tier": resolved_tier,
|
|
76
|
+
**budgets,
|
|
77
|
+
"note": note,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ── Request/Response Models ─────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
class TaskCompleteRequest(BaseModel):
|
|
84
|
+
summary: str
|
|
85
|
+
files_changed: List[str] = Field(default_factory=list)
|
|
86
|
+
|
|
87
|
+
class TaskHandoffRequest(BaseModel):
|
|
88
|
+
to_agent: str
|
|
89
|
+
notes: str = ""
|
|
90
|
+
|
|
91
|
+
class HeartbeatRequest(BaseModel):
|
|
92
|
+
status: str = "idle" # idle | working | error
|
|
93
|
+
current_task_id: Optional[str] = None
|
|
94
|
+
progress: float = 0.0
|
|
95
|
+
message: str = ""
|
|
96
|
+
|
|
97
|
+
class ArtifactUploadRequest(BaseModel):
|
|
98
|
+
task_id: Optional[str] = None
|
|
99
|
+
files: List[Dict[str, str]] # [{"path": "src/app.py", "content": "..."}]
|
|
100
|
+
commit_message: str = "Agent artifact upload"
|
|
101
|
+
|
|
102
|
+
class TaskCreateRequest(BaseModel):
|
|
103
|
+
title: str
|
|
104
|
+
description: str = ""
|
|
105
|
+
priority: str = "medium"
|
|
106
|
+
assigned_to: str = ""
|
|
107
|
+
tags: str = ""
|
|
108
|
+
depends_on: str = ""
|
|
109
|
+
parent_task: str = ""
|
|
110
|
+
|
|
111
|
+
class GraphEdgeRequest(BaseModel):
|
|
112
|
+
source: str
|
|
113
|
+
target: str
|
|
114
|
+
label: str
|
|
115
|
+
properties: Dict[str, Any] = Field(default_factory=dict)
|
|
116
|
+
|
|
117
|
+
class GraphSearchRequest(BaseModel):
|
|
118
|
+
label: str = ""
|
|
119
|
+
query: str = ""
|
|
120
|
+
where: Dict[str, Any] = Field(default_factory=dict)
|
|
121
|
+
limit: int = 10
|
|
122
|
+
|
|
123
|
+
class PushEventRequest(BaseModel):
|
|
124
|
+
branch: str
|
|
125
|
+
commit_sha: str = ""
|
|
126
|
+
files_changed: List[str] = Field(default_factory=list)
|
|
127
|
+
task_id: Optional[str] = None
|
|
128
|
+
|
|
129
|
+
class GraphNodeRequest(BaseModel):
|
|
130
|
+
label: str
|
|
131
|
+
properties: Dict[str, Any] = Field(default_factory=dict)
|
|
132
|
+
|
|
133
|
+
class GraphQueryRequest(BaseModel):
|
|
134
|
+
aiql: str
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ── Module-level stubs for internal helpers (patchable in tests) ─────
|
|
138
|
+
# These are overridden inside create_agent_worker_router closures but
|
|
139
|
+
# exposed here so unit tests can patch them via their module path.
|
|
140
|
+
def _get_session_or_404(session_id: str): # pragma: no cover
|
|
141
|
+
raise NotImplementedError("call create_agent_worker_router first")
|
|
142
|
+
|
|
143
|
+
def _get_connection(session): # pragma: no cover
|
|
144
|
+
raise NotImplementedError("call create_agent_worker_router first")
|
|
145
|
+
|
|
146
|
+
def _get_runtime_connection(session): # pragma: no cover
|
|
147
|
+
raise NotImplementedError("call create_agent_worker_router first")
|
|
148
|
+
|
|
149
|
+
def _get_project_graph(session): # pragma: no cover
|
|
150
|
+
raise NotImplementedError("call create_agent_worker_router first")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ── Router Factory ──────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
def create_agent_worker_router(graph_registry=None) -> APIRouter:
|
|
156
|
+
"""Build the /agent router for remote agent workers."""
|
|
157
|
+
|
|
158
|
+
router = APIRouter(prefix="/agent", tags=["Agent Worker"])
|
|
159
|
+
|
|
160
|
+
from ..context.store_factory import create_agent_registry
|
|
161
|
+
from ..context.session import ContextSessionManager
|
|
162
|
+
from .auth import AgentAuth
|
|
163
|
+
from .events import event_bus
|
|
164
|
+
|
|
165
|
+
agent_registry = create_agent_registry()
|
|
166
|
+
session_manager = ContextSessionManager(graph_registry=graph_registry)
|
|
167
|
+
require_agent = AgentAuth(agent_registry)
|
|
168
|
+
|
|
169
|
+
def _get_session_or_404(session_id: str):
|
|
170
|
+
session = session_manager.get_session(session_id)
|
|
171
|
+
if not session:
|
|
172
|
+
raise HTTPException(404, f"Session '{session_id}' not found")
|
|
173
|
+
return session
|
|
174
|
+
|
|
175
|
+
def _get_project_graph(session):
|
|
176
|
+
"""ProjectGraph targets the runtime namespace (tasks, findings, actions)."""
|
|
177
|
+
from ..project.graph import ProjectGraph
|
|
178
|
+
from ..adapters._base import AIContextDBConnection
|
|
179
|
+
rt_ns = getattr(session, 'runtime_namespace', '') or f"{session.graph_namespace}_rt"
|
|
180
|
+
conn = AIContextDBConnection(namespace=rt_ns, graph_registry=graph_registry)
|
|
181
|
+
return ProjectGraph(rt_ns, connection=conn, agent_registry=agent_registry)
|
|
182
|
+
|
|
183
|
+
def _get_connection(session):
|
|
184
|
+
"""Connection targets the atomic namespace (source knowledge)."""
|
|
185
|
+
from ..adapters._base import AIContextDBConnection
|
|
186
|
+
return AIContextDBConnection(namespace=session.graph_namespace, graph_registry=graph_registry)
|
|
187
|
+
|
|
188
|
+
def _get_runtime_connection(session):
|
|
189
|
+
"""Connection to the runtime graph (agent work)."""
|
|
190
|
+
from ..adapters._base import AIContextDBConnection
|
|
191
|
+
rt_ns = getattr(session, 'runtime_namespace', '') or f"{session.graph_namespace}_rt"
|
|
192
|
+
return AIContextDBConnection(namespace=rt_ns, graph_registry=graph_registry)
|
|
193
|
+
|
|
194
|
+
# ── Session Discovery ───────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
@router.get("/sessions")
|
|
197
|
+
async def list_my_sessions(agent=Depends(require_agent)):
|
|
198
|
+
"""List sessions this agent has access to, with pending task counts."""
|
|
199
|
+
all_sessions = session_manager.list_sessions()
|
|
200
|
+
results = []
|
|
201
|
+
for s in all_sessions:
|
|
202
|
+
if s.status != "active":
|
|
203
|
+
continue
|
|
204
|
+
access_list = session_manager.get_access_list(s.session_id)
|
|
205
|
+
agent_ids = [a["agent_id"] for a in access_list if isinstance(a, dict)]
|
|
206
|
+
if agent.agent_id in agent_ids:
|
|
207
|
+
# Count pending tasks
|
|
208
|
+
try:
|
|
209
|
+
pg = _get_project_graph(s)
|
|
210
|
+
open_tasks = pg.get_open_tasks(agent_id=agent.agent_id, agent_name=agent.name)
|
|
211
|
+
all_open = pg.get_open_tasks()
|
|
212
|
+
unassigned = [t for t in all_open if t["assigned_to"] in ("unassigned", "")]
|
|
213
|
+
except Exception:
|
|
214
|
+
open_tasks, unassigned = [], []
|
|
215
|
+
|
|
216
|
+
results.append({
|
|
217
|
+
"session_id": s.session_id,
|
|
218
|
+
"name": s.name,
|
|
219
|
+
"status": s.status,
|
|
220
|
+
"graph_namespace": s.graph_namespace,
|
|
221
|
+
"my_tasks": len(open_tasks),
|
|
222
|
+
"unassigned_tasks": len(unassigned),
|
|
223
|
+
"goal": (s.config or {}).get("goal", ""),
|
|
224
|
+
})
|
|
225
|
+
return {"sessions": results}
|
|
226
|
+
|
|
227
|
+
# ── Task Polling ────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
@router.get("/sessions/{session_id}/tasks/mine")
|
|
230
|
+
async def my_tasks(
|
|
231
|
+
session_id: str,
|
|
232
|
+
status: str = Query("all", description="open|in_progress|all"),
|
|
233
|
+
agent=Depends(require_agent),
|
|
234
|
+
):
|
|
235
|
+
"""Get tasks assigned to this agent."""
|
|
236
|
+
session = _get_session_or_404(session_id)
|
|
237
|
+
pg = _get_project_graph(session)
|
|
238
|
+
tasks = pg.get_open_tasks(agent_id=agent.agent_id, agent_name=agent.name)
|
|
239
|
+
if status != "all":
|
|
240
|
+
tasks = [t for t in tasks if t["status"] == status]
|
|
241
|
+
return {"tasks": tasks, "count": len(tasks)}
|
|
242
|
+
|
|
243
|
+
@router.get("/sessions/{session_id}/tasks/available")
|
|
244
|
+
async def available_tasks(
|
|
245
|
+
session_id: str,
|
|
246
|
+
priority: str = Query("", description="Filter by priority"),
|
|
247
|
+
agent=Depends(require_agent),
|
|
248
|
+
):
|
|
249
|
+
"""Get open unassigned tasks available to claim."""
|
|
250
|
+
session = _get_session_or_404(session_id)
|
|
251
|
+
pg = _get_project_graph(session)
|
|
252
|
+
all_open = pg.get_open_tasks()
|
|
253
|
+
available = [t for t in all_open if t["assigned_to"] in ("unassigned", "", None)]
|
|
254
|
+
if priority:
|
|
255
|
+
available = [t for t in available if t["priority"] == priority]
|
|
256
|
+
return {"tasks": available, "count": len(available)}
|
|
257
|
+
|
|
258
|
+
@router.get("/sessions/{session_id}/tasks/stream")
|
|
259
|
+
async def stream_tasks(session_id: str, agent=Depends(require_agent)):
|
|
260
|
+
"""SSE endpoint — push task notifications to agents in real-time.
|
|
261
|
+
|
|
262
|
+
Instead of polling /tasks/available every N seconds, agents connect here
|
|
263
|
+
and receive instant notifications when tasks are created or unblocked.
|
|
264
|
+
Falls back: if no events within 30s, sends a heartbeat with pending count.
|
|
265
|
+
"""
|
|
266
|
+
import asyncio
|
|
267
|
+
from starlette.responses import StreamingResponse
|
|
268
|
+
|
|
269
|
+
session = _get_session_or_404(session_id)
|
|
270
|
+
|
|
271
|
+
async def event_generator():
|
|
272
|
+
queue = event_bus.subscribe()
|
|
273
|
+
try:
|
|
274
|
+
while True:
|
|
275
|
+
try:
|
|
276
|
+
event = await asyncio.wait_for(queue.get(), timeout=30)
|
|
277
|
+
if event.get("type") in ("task_available", "task_unblocked", "task_created"):
|
|
278
|
+
yield f"data: {json.dumps(event)}\n\n"
|
|
279
|
+
except asyncio.TimeoutError:
|
|
280
|
+
# Heartbeat with pending task count
|
|
281
|
+
pg = _get_project_graph(session)
|
|
282
|
+
open_tasks = pg.get_open_tasks()
|
|
283
|
+
available = [t for t in open_tasks if t["assigned_to"] in ("unassigned", "", None)]
|
|
284
|
+
yield f"data: {json.dumps({'type': 'heartbeat', 'available': len(available)})}\n\n"
|
|
285
|
+
except asyncio.CancelledError:
|
|
286
|
+
pass
|
|
287
|
+
finally:
|
|
288
|
+
event_bus.unsubscribe(queue)
|
|
289
|
+
|
|
290
|
+
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
|
291
|
+
|
|
292
|
+
# ── Task Lifecycle ──────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
@router.post("/sessions/{session_id}/tasks/{task_id}/claim")
|
|
295
|
+
async def claim_task(session_id: str, task_id: str, agent=Depends(require_agent)):
|
|
296
|
+
"""Claim an open task."""
|
|
297
|
+
session = _get_session_or_404(session_id)
|
|
298
|
+
pg = _get_project_graph(session)
|
|
299
|
+
result = pg.claim_task(task_id, agent.agent_id)
|
|
300
|
+
if result.startswith("Error"):
|
|
301
|
+
raise HTTPException(409, result)
|
|
302
|
+
agent_registry.touch(agent.agent_id)
|
|
303
|
+
return {"status": "claimed", "task_id": task_id, "message": result}
|
|
304
|
+
|
|
305
|
+
@router.post("/sessions/{session_id}/tasks/{task_id}/complete")
|
|
306
|
+
async def complete_task(
|
|
307
|
+
session_id: str, task_id: str, req: TaskCompleteRequest, agent=Depends(require_agent),
|
|
308
|
+
):
|
|
309
|
+
"""Report task completion."""
|
|
310
|
+
session = _get_session_or_404(session_id)
|
|
311
|
+
pg = _get_project_graph(session)
|
|
312
|
+
result = pg.complete_task(
|
|
313
|
+
task_id, agent.agent_id, summary=req.summary,
|
|
314
|
+
files_changed=req.files_changed or None,
|
|
315
|
+
)
|
|
316
|
+
if result.startswith("Error"):
|
|
317
|
+
raise HTTPException(400, result)
|
|
318
|
+
agent_registry.touch(agent.agent_id)
|
|
319
|
+
return {"status": "completed", "task_id": task_id, "message": result}
|
|
320
|
+
|
|
321
|
+
@router.post("/sessions/{session_id}/tasks/{task_id}/handoff")
|
|
322
|
+
async def handoff_task(
|
|
323
|
+
session_id: str, task_id: str, req: TaskHandoffRequest, agent=Depends(require_agent),
|
|
324
|
+
):
|
|
325
|
+
"""Hand off a task to another agent."""
|
|
326
|
+
session = _get_session_or_404(session_id)
|
|
327
|
+
pg = _get_project_graph(session)
|
|
328
|
+
result = pg.handoff_task(task_id, from_agent=agent.agent_id, to_agent=req.to_agent, notes=req.notes)
|
|
329
|
+
if result.startswith("Error"):
|
|
330
|
+
raise HTTPException(400, result)
|
|
331
|
+
return {"status": "handed_off", "task_id": task_id, "message": result}
|
|
332
|
+
|
|
333
|
+
@router.post("/sessions/{session_id}/tasks")
|
|
334
|
+
async def create_task(session_id: str, req: TaskCreateRequest, agent=Depends(require_agent)):
|
|
335
|
+
"""Create a new task (or break a task into subtasks)."""
|
|
336
|
+
session = _get_session_or_404(session_id)
|
|
337
|
+
pg = _get_project_graph(session)
|
|
338
|
+
dep_list = [d.strip() for d in req.depends_on.split(",") if d.strip()] if req.depends_on else None
|
|
339
|
+
tag_list = [t.strip() for t in req.tags.split(",") if t.strip()] if req.tags else None
|
|
340
|
+
task_id = pg.add_task(
|
|
341
|
+
title=req.title, description=req.description,
|
|
342
|
+
priority=req.priority, assigned_to=req.assigned_to or None,
|
|
343
|
+
tags=tag_list, depends_on=dep_list,
|
|
344
|
+
parent_task=req.parent_task or None,
|
|
345
|
+
created_by=agent.name,
|
|
346
|
+
)
|
|
347
|
+
return {"task_id": task_id, "title": req.title, "assigned_to": req.assigned_to, "parent_task": req.parent_task}
|
|
348
|
+
|
|
349
|
+
@router.get("/sessions/{session_id}/tasks")
|
|
350
|
+
async def list_all_tasks(
|
|
351
|
+
session_id: str,
|
|
352
|
+
status: str = Query("", description="open|in_progress|completed|all"),
|
|
353
|
+
agent=Depends(require_agent),
|
|
354
|
+
):
|
|
355
|
+
"""List all tasks in the session."""
|
|
356
|
+
session = _get_session_or_404(session_id)
|
|
357
|
+
pg = _get_project_graph(session)
|
|
358
|
+
tasks = pg.get_open_tasks()
|
|
359
|
+
if status and status != "all":
|
|
360
|
+
tasks = [t for t in tasks if t["status"] == status]
|
|
361
|
+
return {"tasks": tasks, "count": len(tasks)}
|
|
362
|
+
|
|
363
|
+
# ── Heartbeat ───────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
@router.post("/sessions/{session_id}/heartbeat")
|
|
366
|
+
async def heartbeat(session_id: str, req: HeartbeatRequest, agent=Depends(require_agent)):
|
|
367
|
+
"""Agent status heartbeat — updates last_seen, returns pending task count."""
|
|
368
|
+
_get_session_or_404(session_id)
|
|
369
|
+
agent_registry.touch(agent.agent_id)
|
|
370
|
+
|
|
371
|
+
# Count pending tasks
|
|
372
|
+
try:
|
|
373
|
+
session = session_manager.get_session(session_id)
|
|
374
|
+
pg = _get_project_graph(session)
|
|
375
|
+
my_tasks = pg.get_open_tasks(agent_id=agent.agent_id, agent_name=agent.name)
|
|
376
|
+
pending = len(my_tasks)
|
|
377
|
+
except Exception:
|
|
378
|
+
pending = 0
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
"ack": True,
|
|
382
|
+
"server_time": datetime.now(timezone.utc).isoformat(),
|
|
383
|
+
"pending_tasks": pending,
|
|
384
|
+
"agent_status": req.status,
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# ── Workspace ───────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
@router.get("/sessions/{session_id}/workspace/config")
|
|
390
|
+
async def get_workspace_config(session_id: str, agent=Depends(require_agent)):
|
|
391
|
+
"""Get workspace configuration for this session."""
|
|
392
|
+
session = _get_session_or_404(session_id)
|
|
393
|
+
config = (session.config or {}).get("workspace", {"type": "local"})
|
|
394
|
+
|
|
395
|
+
# Add computed agent-specific branch name
|
|
396
|
+
branch_pattern = config.get("branch_pattern", "agent/{agent_name}/{task_id}")
|
|
397
|
+
config["agent_branch_prefix"] = f"agent/{agent.name}"
|
|
398
|
+
|
|
399
|
+
# Remove sensitive fields for non-admin agents
|
|
400
|
+
safe_config = dict(config)
|
|
401
|
+
if agent.role != "admin" and "admin" not in (agent.capabilities or []):
|
|
402
|
+
safe_config.pop("github_token", None)
|
|
403
|
+
|
|
404
|
+
return {"workspace": safe_config, "session_name": session.name}
|
|
405
|
+
|
|
406
|
+
@router.post("/sessions/{session_id}/workspace/artifacts")
|
|
407
|
+
async def upload_artifacts(
|
|
408
|
+
session_id: str, req: ArtifactUploadRequest, agent=Depends(require_agent),
|
|
409
|
+
):
|
|
410
|
+
"""Upload files for agents that can't use git directly."""
|
|
411
|
+
session = _get_session_or_404(session_id)
|
|
412
|
+
ws_config = (session.config or {}).get("workspace", {"type": "local", "path": f"generated/{session.name.replace(' ', '-').lower()}"})
|
|
413
|
+
|
|
414
|
+
from ..workspace.base import Workspace
|
|
415
|
+
ws = Workspace.from_config(ws_config)
|
|
416
|
+
|
|
417
|
+
written = []
|
|
418
|
+
for f in req.files:
|
|
419
|
+
result = ws.write_file(f["path"], f["content"])
|
|
420
|
+
written.append(f["path"])
|
|
421
|
+
|
|
422
|
+
# Commit if git workspace
|
|
423
|
+
commit_result = ws.commit(req.commit_message)
|
|
424
|
+
|
|
425
|
+
# Record in graph
|
|
426
|
+
conn = _get_connection(session)
|
|
427
|
+
conn.add_node(label="ArtifactUpload", properties={
|
|
428
|
+
"agent_id": agent.agent_id,
|
|
429
|
+
"agent_name": agent.name,
|
|
430
|
+
"files": ",".join(written),
|
|
431
|
+
"task_id": req.task_id or "",
|
|
432
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
return {"status": "received", "files_written": len(written), "commit": commit_result}
|
|
436
|
+
|
|
437
|
+
@router.post("/sessions/{session_id}/workspace/push-event")
|
|
438
|
+
async def push_event(
|
|
439
|
+
session_id: str, req: PushEventRequest, agent=Depends(require_agent),
|
|
440
|
+
):
|
|
441
|
+
"""Notify server that agent pushed to the git remote."""
|
|
442
|
+
session = _get_session_or_404(session_id)
|
|
443
|
+
conn = _get_runtime_connection(session) # agent action → runtime graph
|
|
444
|
+
|
|
445
|
+
# Record push event in graph
|
|
446
|
+
conn.add_node(label="GitPush", properties={
|
|
447
|
+
"agent_id": agent.agent_id,
|
|
448
|
+
"agent_name": agent.name,
|
|
449
|
+
"branch": req.branch,
|
|
450
|
+
"commit_sha": req.commit_sha,
|
|
451
|
+
"files_changed": ",".join(req.files_changed),
|
|
452
|
+
"task_id": req.task_id or "",
|
|
453
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
agent_registry.touch(agent.agent_id)
|
|
457
|
+
return {"status": "recorded", "branch": req.branch}
|
|
458
|
+
|
|
459
|
+
# ── Graph Access ────────────────────────────────────────────────
|
|
460
|
+
|
|
461
|
+
@router.post("/sessions/{session_id}/graph/nodes")
|
|
462
|
+
async def add_graph_node(
|
|
463
|
+
session_id: str, req: GraphNodeRequest, agent=Depends(require_agent),
|
|
464
|
+
):
|
|
465
|
+
"""Add a knowledge node to the session's graph."""
|
|
466
|
+
session = _get_session_or_404(session_id)
|
|
467
|
+
# All agent writes go to runtime graph — agents never write directly to atomic.
|
|
468
|
+
# Atomic context is populated only by the ingestion pipeline.
|
|
469
|
+
# Nodes keep their label (Fact, Knowledge, etc.) but live in runtime
|
|
470
|
+
# until explicitly promoted to atomic.
|
|
471
|
+
conn = _get_runtime_connection(session)
|
|
472
|
+
|
|
473
|
+
props = dict(req.properties)
|
|
474
|
+
props["_agent_id"] = agent.agent_id
|
|
475
|
+
props["_agent_name"] = agent.name
|
|
476
|
+
|
|
477
|
+
# Use AIQL to ensure node goes to the correct namespace
|
|
478
|
+
prop_parts = []
|
|
479
|
+
for k, v in props.items():
|
|
480
|
+
if isinstance(v, str):
|
|
481
|
+
escaped = v.replace('"', '\\"')
|
|
482
|
+
prop_parts.append(f'{k}: "{escaped}"')
|
|
483
|
+
elif isinstance(v, bool):
|
|
484
|
+
prop_parts.append(f'{k}: {"TRUE" if v else "FALSE"}')
|
|
485
|
+
elif isinstance(v, (int, float)):
|
|
486
|
+
prop_parts.append(f'{k}: {v}')
|
|
487
|
+
else:
|
|
488
|
+
escaped = str(v).replace('"', '\\"')
|
|
489
|
+
prop_parts.append(f'{k}: "{escaped}"')
|
|
490
|
+
prop_str = ", ".join(prop_parts)
|
|
491
|
+
result = conn.query(f'CREATE NODE {req.label} {{{prop_str}}}')
|
|
492
|
+
node_id = result.get("data", {}).get("uuid", "")
|
|
493
|
+
return {"node_id": node_id, "label": req.label}
|
|
494
|
+
|
|
495
|
+
@router.post("/sessions/{session_id}/graph/query")
|
|
496
|
+
async def query_graph(
|
|
497
|
+
session_id: str, req: GraphQueryRequest, agent=Depends(require_agent),
|
|
498
|
+
):
|
|
499
|
+
"""Execute an AIQL query against the session's graph."""
|
|
500
|
+
session = _get_session_or_404(session_id)
|
|
501
|
+
conn = _get_connection(session)
|
|
502
|
+
result = conn.query(req.aiql)
|
|
503
|
+
return result
|
|
504
|
+
|
|
505
|
+
@router.post("/sessions/{session_id}/graph/edges")
|
|
506
|
+
async def add_graph_edge(
|
|
507
|
+
session_id: str, req: GraphEdgeRequest, agent=Depends(require_agent),
|
|
508
|
+
):
|
|
509
|
+
"""Create an edge between two nodes."""
|
|
510
|
+
session = _get_session_or_404(session_id)
|
|
511
|
+
conn = _get_connection(session)
|
|
512
|
+
props = dict(req.properties)
|
|
513
|
+
props["_agent_id"] = agent.agent_id
|
|
514
|
+
edge = conn.add_edge(source=req.source, target=req.target, label=req.label, properties=props)
|
|
515
|
+
return {"edge_id": edge.id, "label": req.label, "source": req.source, "target": req.target}
|
|
516
|
+
|
|
517
|
+
@router.post("/sessions/{session_id}/graph/search")
|
|
518
|
+
async def search_graph_nodes(
|
|
519
|
+
session_id: str, req: GraphSearchRequest, agent=Depends(require_agent),
|
|
520
|
+
):
|
|
521
|
+
"""Search nodes by label, query text, and/or property filters."""
|
|
522
|
+
session = _get_session_or_404(session_id)
|
|
523
|
+
conn = _get_connection(session)
|
|
524
|
+
|
|
525
|
+
# If query text provided, use search_nodes tool (keyword + semantic)
|
|
526
|
+
if req.query and req.query.strip():
|
|
527
|
+
try:
|
|
528
|
+
from ..tools import ToolRegistry
|
|
529
|
+
from .playground_conn import PlaygroundConn, create_tool_context
|
|
530
|
+
_conn = PlaygroundConn(session.graph_namespace, graph_registry)
|
|
531
|
+
_ctx = create_tool_context(_conn, agent.agent_id, agent.name)
|
|
532
|
+
params = {"query": req.query, "limit": str(req.limit)}
|
|
533
|
+
if req.label:
|
|
534
|
+
params["label"] = req.label
|
|
535
|
+
result_text = ToolRegistry.dispatch("search_nodes", _ctx, params)
|
|
536
|
+
return {"results": result_text, "query": req.query, "label": req.label}
|
|
537
|
+
except Exception:
|
|
538
|
+
pass # fallback to AIQL
|
|
539
|
+
|
|
540
|
+
# Fallback: AIQL SELECT
|
|
541
|
+
from ..security.sanitize import sanitize_aiql_identifier, build_safe_where_clause
|
|
542
|
+
where_clause = build_safe_where_clause(req.where)
|
|
543
|
+
safe_label = sanitize_aiql_identifier(req.label) if req.label else ""
|
|
544
|
+
query = f"SELECT * FROM {safe_label}{where_clause}" if safe_label else f"SELECT *{where_clause}"
|
|
545
|
+
result = conn.query(query)
|
|
546
|
+
nodes = result.get("nodes", [])
|
|
547
|
+
# Limit results and truncate content to prevent ResponseTooLargeError
|
|
548
|
+
limit = req.limit if hasattr(req, 'limit') and req.limit else 10
|
|
549
|
+
truncated = []
|
|
550
|
+
for n in nodes[:limit]:
|
|
551
|
+
if isinstance(n, dict):
|
|
552
|
+
props = n.get("properties", n)
|
|
553
|
+
truncated.append({
|
|
554
|
+
"label": n.get("label", n.get("node_type", "?")),
|
|
555
|
+
"name": (props.get("name", "") or "")[:100],
|
|
556
|
+
"content": (props.get("content", props.get("statement", "")) or "")[:200],
|
|
557
|
+
})
|
|
558
|
+
else:
|
|
559
|
+
props = n.properties if hasattr(n, 'properties') else {}
|
|
560
|
+
truncated.append({
|
|
561
|
+
"label": n.label if hasattr(n, 'label') else "?",
|
|
562
|
+
"name": (props.get("name", "") or "")[:100],
|
|
563
|
+
"content": (props.get("content", props.get("statement", "")) or "")[:200],
|
|
564
|
+
})
|
|
565
|
+
return {"nodes": truncated, "count": len(nodes), "showing": len(truncated)}
|
|
566
|
+
|
|
567
|
+
# ── Agent Activity ─────────────────────────────────────────
|
|
568
|
+
|
|
569
|
+
@router.get("/sessions/{session_id}/activity")
|
|
570
|
+
async def agent_session_activity(
|
|
571
|
+
session_id: str, limit: int = Query(50), agent=Depends(require_agent),
|
|
572
|
+
):
|
|
573
|
+
"""Get agent activity for this session — who wrote what, when."""
|
|
574
|
+
session = _get_session_or_404(session_id)
|
|
575
|
+
conn = _get_connection(session)
|
|
576
|
+
|
|
577
|
+
# Find all nodes with _agent_id or _agent_name or created_by
|
|
578
|
+
activity = []
|
|
579
|
+
try:
|
|
580
|
+
for label in ["Finding", "Insight", "Decision", "Task", "ToolCall"]:
|
|
581
|
+
nodes = conn.db.get_all_nodes(label=label) if hasattr(conn, 'db') and conn.db else []
|
|
582
|
+
for n in nodes:
|
|
583
|
+
props = n.properties if hasattr(n, 'properties') else {}
|
|
584
|
+
agent_name = props.get("_agent_name", props.get("created_by", props.get("source", "")))
|
|
585
|
+
if agent_name:
|
|
586
|
+
activity.append({
|
|
587
|
+
"id": n.id if hasattr(n, 'id') else "",
|
|
588
|
+
"label": n.label if hasattr(n, 'label') else label,
|
|
589
|
+
"agent": agent_name,
|
|
590
|
+
"name": (props.get("name", "") or "")[:100],
|
|
591
|
+
"content": (props.get("content", props.get("statement", "")) or "")[:200],
|
|
592
|
+
"created_at": props.get("created_at", ""),
|
|
593
|
+
})
|
|
594
|
+
except Exception:
|
|
595
|
+
pass
|
|
596
|
+
|
|
597
|
+
# Sort by created_at descending
|
|
598
|
+
activity.sort(key=lambda x: x.get("created_at", ""), reverse=True)
|
|
599
|
+
return {"activity": activity[:limit], "total": len(activity)}
|
|
600
|
+
|
|
601
|
+
# ── Context Gaps ──────────────────────────────────────────
|
|
602
|
+
|
|
603
|
+
@router.get("/sessions/{session_id}/gaps")
|
|
604
|
+
async def get_context_gaps(session_id: str, limit: int = Query(20), agent=Depends(require_agent)):
|
|
605
|
+
"""Get top context gaps — what agents searched for but couldn't find."""
|
|
606
|
+
session = _get_session_or_404(session_id)
|
|
607
|
+
from ..intelligence.context_gaps import get_top_gaps, get_gap_stats
|
|
608
|
+
gaps = get_top_gaps(namespace=session.graph_namespace, limit=limit)
|
|
609
|
+
stats = get_gap_stats(namespace=session.graph_namespace)
|
|
610
|
+
return {"gaps": gaps, "stats": stats}
|
|
611
|
+
|
|
612
|
+
# ── GPT Action Schema (auto-generated) ──────────────────────
|
|
613
|
+
|
|
614
|
+
@router.get("/openapi.json")
|
|
615
|
+
async def agent_openapi_schema(request: Request):
|
|
616
|
+
"""Auto-generated OpenAPI schema for GPT Custom Actions.
|
|
617
|
+
|
|
618
|
+
ChatGPT can import this directly:
|
|
619
|
+
1. In GPT editor → Actions → Import URL
|
|
620
|
+
2. Paste: https://your-server/agent/openapi.json
|
|
621
|
+
"""
|
|
622
|
+
# Build absolute URL from request (works behind ngrok/proxy)
|
|
623
|
+
forwarded_host = request.headers.get("x-forwarded-host", "")
|
|
624
|
+
forwarded_proto = request.headers.get("x-forwarded-proto", "https")
|
|
625
|
+
if forwarded_host:
|
|
626
|
+
host = f"{forwarded_proto}://{forwarded_host}"
|
|
627
|
+
else:
|
|
628
|
+
host = f"{request.url.scheme}://{request.url.netloc}"
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
"openapi": "3.1.0",
|
|
632
|
+
"info": {
|
|
633
|
+
"title": "AIContextDB Agent API",
|
|
634
|
+
"description": "Shared knowledge graph for AI agents. Search, write, dispatch goals.",
|
|
635
|
+
"version": "1.1.0",
|
|
636
|
+
},
|
|
637
|
+
"servers": [{"url": f"{host}/agent" if host else "/agent"}],
|
|
638
|
+
"paths": {
|
|
639
|
+
"/sessions/{session_id}/graph/search": {
|
|
640
|
+
"post": {
|
|
641
|
+
"operationId": "searchGraph",
|
|
642
|
+
"summary": "Search the knowledge graph",
|
|
643
|
+
"parameters": [{"name": "session_id", "in": "path", "required": True, "schema": {"type": "string"}}],
|
|
644
|
+
"requestBody": {"required": True, "content": {"application/json": {"schema": {
|
|
645
|
+
"type": "object",
|
|
646
|
+
"properties": {
|
|
647
|
+
"label": {"type": "string", "description": "Node type filter"},
|
|
648
|
+
"query": {"type": "string", "description": "Search keywords"},
|
|
649
|
+
"limit": {"type": "integer", "default": 10},
|
|
650
|
+
},
|
|
651
|
+
}}}},
|
|
652
|
+
"responses": {"200": {"description": "Search results"}},
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
"/sessions/{session_id}/graph/nodes": {
|
|
656
|
+
"post": {
|
|
657
|
+
"operationId": "addNode",
|
|
658
|
+
"summary": "Add a node to the graph",
|
|
659
|
+
"parameters": [{"name": "session_id", "in": "path", "required": True, "schema": {"type": "string"}}],
|
|
660
|
+
"requestBody": {"required": True, "content": {"application/json": {"schema": {
|
|
661
|
+
"type": "object",
|
|
662
|
+
"properties": {
|
|
663
|
+
"label": {"type": "string", "default": "Finding"},
|
|
664
|
+
"properties": {"type": "object", "properties": {
|
|
665
|
+
"name": {"type": "string"},
|
|
666
|
+
"content": {"type": "string"},
|
|
667
|
+
}, "required": ["name"]},
|
|
668
|
+
},
|
|
669
|
+
"required": ["label", "properties"],
|
|
670
|
+
}}}},
|
|
671
|
+
"responses": {"200": {"description": "Created node"}},
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
"/sessions/{session_id}/dispatch": {
|
|
675
|
+
"post": {
|
|
676
|
+
"operationId": "dispatchGoal",
|
|
677
|
+
"summary": "Decompose a goal into tasks and assign to agents",
|
|
678
|
+
"parameters": [{"name": "session_id", "in": "path", "required": True, "schema": {"type": "string"}}],
|
|
679
|
+
"requestBody": {"required": True, "content": {"application/json": {"schema": {
|
|
680
|
+
"type": "object",
|
|
681
|
+
"properties": {"goal": {"type": "string"}},
|
|
682
|
+
"required": ["goal"],
|
|
683
|
+
}}}},
|
|
684
|
+
"responses": {"200": {"description": "Tasks created"}},
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
"/sessions/{session_id}/graph/query": {
|
|
688
|
+
"post": {
|
|
689
|
+
"operationId": "queryGraph",
|
|
690
|
+
"summary": "Execute an AIQL query",
|
|
691
|
+
"parameters": [{"name": "session_id", "in": "path", "required": True, "schema": {"type": "string"}}],
|
|
692
|
+
"requestBody": {"required": True, "content": {"application/json": {"schema": {
|
|
693
|
+
"type": "object",
|
|
694
|
+
"properties": {"aiql": {"type": "string"}},
|
|
695
|
+
"required": ["aiql"],
|
|
696
|
+
}}}},
|
|
697
|
+
"responses": {"200": {"description": "Query results"}},
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
# ── Goal Dispatch ─────────────────────────────────────────────
|
|
704
|
+
|
|
705
|
+
class DispatchGoalRequest(BaseModel):
|
|
706
|
+
goal: str
|
|
707
|
+
session_id: str = ""
|
|
708
|
+
|
|
709
|
+
@router.post("/sessions/{session_id}/dispatch")
|
|
710
|
+
async def dispatch_agent_goal(
|
|
711
|
+
session_id: str, req: DispatchGoalRequest, agent=Depends(require_agent),
|
|
712
|
+
):
|
|
713
|
+
"""Decompose a goal into tasks and auto-assign to agents."""
|
|
714
|
+
from ..agents.dispatcher import dispatch_goal as _dispatch
|
|
715
|
+
from ..context.store_factory import create_agent_registry
|
|
716
|
+
from ..context.session import ContextSessionManager
|
|
717
|
+
|
|
718
|
+
llm_fn = None
|
|
719
|
+
try:
|
|
720
|
+
from ..llm import get_llm_client
|
|
721
|
+
llm = get_llm_client()
|
|
722
|
+
llm_fn = lambda prompt: llm.generate(prompt=prompt, max_tokens=500)
|
|
723
|
+
except Exception:
|
|
724
|
+
pass
|
|
725
|
+
|
|
726
|
+
result = _dispatch(
|
|
727
|
+
goal=req.goal,
|
|
728
|
+
session_id=session_id,
|
|
729
|
+
graph_registry=graph_registry,
|
|
730
|
+
agent_registry=create_agent_registry(),
|
|
731
|
+
session_manager=ContextSessionManager(),
|
|
732
|
+
llm_fn=llm_fn,
|
|
733
|
+
)
|
|
734
|
+
return result
|
|
735
|
+
|
|
736
|
+
# ── Agent Context ───────────────────────────────────────────────
|
|
737
|
+
|
|
738
|
+
@router.get("/sessions/{session_id}/orient")
|
|
739
|
+
async def orient_agent(
|
|
740
|
+
session_id: str,
|
|
741
|
+
max_tokens: int = Query(2000, description="Max projection budget"),
|
|
742
|
+
max_time_ms: int = Query(1000, description="Max time budget in ms"),
|
|
743
|
+
tier: Optional[str] = Query(None, description="Projection tier: instant|fast|standard|deep. Defaults to platform-appropriate tier."),
|
|
744
|
+
agent=Depends(require_agent),
|
|
745
|
+
):
|
|
746
|
+
"""Contextual Graph Projection — queryless context delivery.
|
|
747
|
+
|
|
748
|
+
Returns task-specific projected context from the shared knowledge graph.
|
|
749
|
+
Tier controls pipeline depth: instant (cache only), fast (BM25),
|
|
750
|
+
standard (BM25+Qdrant+hops), deep (all stages+signals+extended).
|
|
751
|
+
"""
|
|
752
|
+
session = _get_session_or_404(session_id)
|
|
753
|
+
conn = _get_connection(session)
|
|
754
|
+
rt_conn = _get_runtime_connection(session)
|
|
755
|
+
pg = _get_project_graph(session)
|
|
756
|
+
|
|
757
|
+
from ..tools.registry import ToolContext
|
|
758
|
+
ctx = ToolContext(
|
|
759
|
+
conn=conn,
|
|
760
|
+
runtime_conn=rt_conn,
|
|
761
|
+
agent_id=agent.agent_id,
|
|
762
|
+
agent_name=agent.name,
|
|
763
|
+
)
|
|
764
|
+
ctx.project = pg
|
|
765
|
+
|
|
766
|
+
tier_was_explicit = tier is not None
|
|
767
|
+
tier = _tier_for_platform(agent.platform, tier)
|
|
768
|
+
hints = _build_platform_hints(agent.platform, tier, tier_was_explicit)
|
|
769
|
+
|
|
770
|
+
# Try CGP projection with budget
|
|
771
|
+
try:
|
|
772
|
+
from ..context.projection import project_context
|
|
773
|
+
projection = project_context(ctx, max_tokens=max_tokens,
|
|
774
|
+
max_time_ms=max_time_ms, tier=tier)
|
|
775
|
+
if projection:
|
|
776
|
+
return {"orient": projection, "mode": "projection", "agent": agent.name,
|
|
777
|
+
"tier": tier, "platform_hints": hints}
|
|
778
|
+
except Exception:
|
|
779
|
+
pass
|
|
780
|
+
|
|
781
|
+
# Fallback: classic graph summary
|
|
782
|
+
try:
|
|
783
|
+
from ..tools.graph import _orient
|
|
784
|
+
result = _orient(ctx)
|
|
785
|
+
return {"orient": result, "mode": "manifest", "agent": agent.name,
|
|
786
|
+
"tier": "fallback", "platform_hints": hints}
|
|
787
|
+
except Exception as e:
|
|
788
|
+
return {"orient": f"Graph: {session.graph_namespace}", "mode": "basic",
|
|
789
|
+
"agent": agent.name, "tier": "fallback", "platform_hints": hints}
|
|
790
|
+
|
|
791
|
+
@router.get("/sessions/{session_id}/context")
|
|
792
|
+
async def get_agent_context(
|
|
793
|
+
session_id: str,
|
|
794
|
+
system_prompt: str = Query("", description="Custom system prompt"),
|
|
795
|
+
max_tokens: int = Query(6000, description="Max token budget"),
|
|
796
|
+
agent=Depends(require_agent),
|
|
797
|
+
):
|
|
798
|
+
"""Get full project context for this agent — requirements, tasks, decisions, code produced.
|
|
799
|
+
|
|
800
|
+
This is the main way an agent understands what it should do and what's happened so far.
|
|
801
|
+
Returns LLM-ready context as a single prompt string.
|
|
802
|
+
"""
|
|
803
|
+
session = _get_session_or_404(session_id)
|
|
804
|
+
pg = _get_project_graph(session)
|
|
805
|
+
context = pg.build_agent_context(
|
|
806
|
+
agent_id=agent.agent_id,
|
|
807
|
+
system_prompt=system_prompt,
|
|
808
|
+
max_tokens=max_tokens,
|
|
809
|
+
)
|
|
810
|
+
return {"context": context, "session": session.name, "agent": agent.name}
|
|
811
|
+
|
|
812
|
+
# ── Natural Language Query ──────────────────────────────────────
|
|
813
|
+
|
|
814
|
+
class AskRequest(BaseModel):
|
|
815
|
+
question: str
|
|
816
|
+
|
|
817
|
+
@router.post("/sessions/{session_id}/graph/ask")
|
|
818
|
+
async def ask_graph(session_id: str, req: AskRequest, agent=Depends(require_agent)):
|
|
819
|
+
"""Ask about the project in plain English. No query syntax needed.
|
|
820
|
+
|
|
821
|
+
Examples: "What are the requirements?", "What has codex done?",
|
|
822
|
+
"Show me the decisions", "What tasks are blocked?"
|
|
823
|
+
"""
|
|
824
|
+
session = _get_session_or_404(session_id)
|
|
825
|
+
conn = _get_connection(session)
|
|
826
|
+
pg = _get_project_graph(session)
|
|
827
|
+
|
|
828
|
+
from ..search.semantic_query import semantic_search
|
|
829
|
+
result = semantic_search(req.question, conn, pg)
|
|
830
|
+
return result
|
|
831
|
+
|
|
832
|
+
# ── Task Context ────────────────────────────────────────────────
|
|
833
|
+
|
|
834
|
+
@router.get("/sessions/{session_id}/tasks/{task_id}/context")
|
|
835
|
+
async def get_task_context(session_id: str, task_id: str, agent=Depends(require_agent)):
|
|
836
|
+
"""Get everything about a specific task: requirements, decisions, code files, dependencies."""
|
|
837
|
+
session = _get_session_or_404(session_id)
|
|
838
|
+
pg = _get_project_graph(session)
|
|
839
|
+
conn = _get_connection(session)
|
|
840
|
+
|
|
841
|
+
node = pg._resolve_task_id(task_id)
|
|
842
|
+
if not node:
|
|
843
|
+
raise HTTPException(404, f"Task {task_id} not found")
|
|
844
|
+
|
|
845
|
+
props = node.properties if hasattr(node, "properties") else {}
|
|
846
|
+
tid = node.id if hasattr(node, "id") else task_id
|
|
847
|
+
|
|
848
|
+
# Find related nodes via edges
|
|
849
|
+
edges = conn.get_edges()
|
|
850
|
+
related = {"requirements": [], "decisions": [], "code_files": [], "subtasks": [], "depends_on": []}
|
|
851
|
+
|
|
852
|
+
for e in edges:
|
|
853
|
+
src = e.source if hasattr(e, "source") else e.get("source", "")
|
|
854
|
+
tgt = e.target if hasattr(e, "target") else e.get("target", "")
|
|
855
|
+
lbl = e.label if hasattr(e, "label") else e.get("label", "")
|
|
856
|
+
|
|
857
|
+
if src == tid or tgt == tid:
|
|
858
|
+
other_id = tgt if src == tid else src
|
|
859
|
+
other = conn.get_node(other_id)
|
|
860
|
+
if other:
|
|
861
|
+
other_label = other.label if hasattr(other, "label") else ""
|
|
862
|
+
other_props = other.properties if hasattr(other, "properties") else {}
|
|
863
|
+
info = {"id": other_id, "label": other_label, **other_props}
|
|
864
|
+
|
|
865
|
+
if other_label == "Requirement":
|
|
866
|
+
related["requirements"].append(info)
|
|
867
|
+
elif other_label == "Decision":
|
|
868
|
+
related["decisions"].append(info)
|
|
869
|
+
elif other_label == "CodeFile":
|
|
870
|
+
related["code_files"].append(info)
|
|
871
|
+
elif other_label == "Task" and lbl == "HAS_SUBTASK":
|
|
872
|
+
related["subtasks"].append(info)
|
|
873
|
+
elif other_label == "Task" and lbl == "DEPENDS_ON":
|
|
874
|
+
related["depends_on"].append(info)
|
|
875
|
+
|
|
876
|
+
return {
|
|
877
|
+
"task": {"id": tid, "label": "Task", **props},
|
|
878
|
+
**related,
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
# ── Promotion ──────────────────────────────────────────────────
|
|
882
|
+
|
|
883
|
+
class PromotionConfigRequest(BaseModel):
|
|
884
|
+
auto_promote_threshold: Optional[float] = None
|
|
885
|
+
review_threshold: Optional[float] = None
|
|
886
|
+
require_human_review: Optional[bool] = None
|
|
887
|
+
weights: Optional[Dict[str, float]] = None
|
|
888
|
+
|
|
889
|
+
class PromotionRejectRequest(BaseModel):
|
|
890
|
+
reason: str = ""
|
|
891
|
+
|
|
892
|
+
@router.get("/sessions/{session_id}/promotion/queue")
|
|
893
|
+
async def promotion_queue(session_id: str, limit: int = Query(50),
|
|
894
|
+
agent=Depends(require_agent)):
|
|
895
|
+
"""Get pending promotion review items."""
|
|
896
|
+
session = _get_session_or_404(session_id)
|
|
897
|
+
from ..context.promotion import PromotionQueue
|
|
898
|
+
queue = PromotionQueue(session.graph_namespace)
|
|
899
|
+
items = queue.pending(limit=limit)
|
|
900
|
+
return {"items": items, "count": queue.count()}
|
|
901
|
+
|
|
902
|
+
@router.post("/sessions/{session_id}/promotion/{node_id}/approve")
|
|
903
|
+
async def approve_promotion(session_id: str, node_id: str,
|
|
904
|
+
agent=Depends(require_agent)):
|
|
905
|
+
"""Approve a node for promotion to atomic context."""
|
|
906
|
+
session = _get_session_or_404(session_id)
|
|
907
|
+
from ..context.promotion import PromotionQueue, promote_node
|
|
908
|
+
|
|
909
|
+
queue = PromotionQueue(session.graph_namespace)
|
|
910
|
+
queue.approve(node_id, agent.agent_id)
|
|
911
|
+
|
|
912
|
+
rt_conn = _get_runtime_connection(session)
|
|
913
|
+
at_conn = _get_connection(session)
|
|
914
|
+
promoted_id = promote_node(node_id, rt_conn, at_conn,
|
|
915
|
+
promoted_by=agent.name)
|
|
916
|
+
|
|
917
|
+
if promoted_id:
|
|
918
|
+
return {"promoted": True, "atomic_node_id": promoted_id,
|
|
919
|
+
"node_id": node_id}
|
|
920
|
+
raise HTTPException(400, f"Failed to promote node {node_id}")
|
|
921
|
+
|
|
922
|
+
@router.post("/sessions/{session_id}/promotion/{node_id}/reject")
|
|
923
|
+
async def reject_promotion(session_id: str, node_id: str,
|
|
924
|
+
req: PromotionRejectRequest,
|
|
925
|
+
agent=Depends(require_agent)):
|
|
926
|
+
"""Reject a node from promotion queue."""
|
|
927
|
+
session = _get_session_or_404(session_id)
|
|
928
|
+
from ..context.promotion import PromotionQueue
|
|
929
|
+
|
|
930
|
+
queue = PromotionQueue(session.graph_namespace)
|
|
931
|
+
queue.reject(node_id, agent.agent_id, reason=req.reason)
|
|
932
|
+
return {"rejected": True, "node_id": node_id}
|
|
933
|
+
|
|
934
|
+
@router.get("/sessions/{session_id}/promotion/config")
|
|
935
|
+
async def get_promotion_config(session_id: str,
|
|
936
|
+
agent=Depends(require_agent)):
|
|
937
|
+
"""Get promotion configuration for this session."""
|
|
938
|
+
session = _get_session_or_404(session_id)
|
|
939
|
+
from ..context.promotion import PROMOTION_DEFAULTS
|
|
940
|
+
config = dict(PROMOTION_DEFAULTS)
|
|
941
|
+
session_promo = (session.config or {}).get("promotion", {})
|
|
942
|
+
config.update(session_promo)
|
|
943
|
+
return {"config": config, "session_id": session_id}
|
|
944
|
+
|
|
945
|
+
@router.put("/sessions/{session_id}/promotion/config")
|
|
946
|
+
async def update_promotion_config(session_id: str,
|
|
947
|
+
req: PromotionConfigRequest,
|
|
948
|
+
agent=Depends(require_agent)):
|
|
949
|
+
"""Update promotion configuration for this session."""
|
|
950
|
+
session = _get_session_or_404(session_id)
|
|
951
|
+
promo_config = (session.config or {}).get("promotion", {})
|
|
952
|
+
|
|
953
|
+
if req.auto_promote_threshold is not None:
|
|
954
|
+
promo_config["auto_promote_threshold"] = req.auto_promote_threshold
|
|
955
|
+
if req.review_threshold is not None:
|
|
956
|
+
promo_config["review_threshold"] = req.review_threshold
|
|
957
|
+
if req.require_human_review is not None:
|
|
958
|
+
promo_config["require_human_review"] = req.require_human_review
|
|
959
|
+
if req.weights is not None:
|
|
960
|
+
promo_config["weights"] = req.weights
|
|
961
|
+
|
|
962
|
+
config = dict(session.config or {})
|
|
963
|
+
config["promotion"] = promo_config
|
|
964
|
+
try:
|
|
965
|
+
import sqlite3, json as _json
|
|
966
|
+
conn = sqlite3.connect(str(session_manager.db_path))
|
|
967
|
+
conn.execute(
|
|
968
|
+
"UPDATE context_sessions SET config = ? WHERE session_id = ?",
|
|
969
|
+
(_json.dumps(config), session.session_id),
|
|
970
|
+
)
|
|
971
|
+
conn.commit()
|
|
972
|
+
conn.close()
|
|
973
|
+
except Exception as e:
|
|
974
|
+
raise HTTPException(500, f"Failed to save config: {e}")
|
|
975
|
+
|
|
976
|
+
return {"updated": True, "config": promo_config}
|
|
977
|
+
|
|
978
|
+
return router
|