ltcai 11.5.2 → 11.7.0
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.
- package/README.md +93 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +119 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +8 -6
- package/docs/ENTERPRISE.md +2 -1
- package/docs/MULTI_AGENT_RUNTIME.md +12 -5
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/REALTIME_COLLABORATION.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/WORKFLOW_DESIGNER.md +3 -2
- package/docs/kg-schema.md +1 -1
- package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
- package/lattice_brain/__init__.py +42 -79
- package/lattice_brain/graph/__init__.py +9 -24
- package/lattice_brain/graph/_kg_common/__init__.py +13 -24
- package/lattice_brain/ingestion/__init__.py +19 -58
- package/lattice_brain/ingestion/pipeline.py +20 -398
- package/lattice_brain/multimodal/__init__.py +7 -18
- package/lattice_brain/multimodal/images.py +6 -264
- package/lattice_brain/multimodal/video.py +8 -247
- package/lattice_brain/runtime/__init__.py +8 -79
- package/lattice_brain/runtime/hooks.py +22 -584
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agent_worker_seam.py +7 -80
- package/latticeai/api/health.py +6 -20
- package/latticeai/api/local_files.py +16 -613
- package/latticeai/api/models.py +12 -87
- package/latticeai/api/search.py +17 -255
- package/latticeai/api/tools.py +69 -750
- package/latticeai/api/voice_capture.py +8 -70
- package/latticeai/api/worker_compute.py +842 -0
- package/latticeai/api/worker_seams.py +216 -0
- package/latticeai/app_factory.py +23 -232
- package/latticeai/cli/entrypoint.py +36 -249
- package/latticeai/core/agent_permission.py +16 -91
- package/latticeai/core/messages.py +89 -531
- package/latticeai/runtime/access_runtime.py +0 -14
- package/latticeai/runtime/bootstrap.py +10 -21
- package/latticeai/runtime/brain_runtime.py +35 -43
- package/latticeai/runtime/build_phases/__init__.py +21 -37
- package/latticeai/runtime/build_phases/features.py +99 -389
- package/latticeai/runtime/build_phases/foundation.py +82 -392
- package/latticeai/runtime/build_phases/web.py +91 -408
- package/latticeai/runtime/build_phases/worker_profile.py +244 -0
- package/latticeai/runtime/lifespan_runtime.py +7 -16
- package/latticeai/runtime/platform_services_runtime.py +1 -15
- package/latticeai/runtime/runtime_context.py +13 -130
- package/latticeai/runtime/security_runtime.py +10 -103
- package/latticeai/services/architecture_readiness.py +82 -43
- package/latticeai/services/model_runtime/__init__.py +2 -11
- package/latticeai/services/model_runtime/service.py +4 -28
- package/latticeai/services/p_reinforce.py +10 -261
- package/latticeai/services/product_readiness.py +31 -38
- package/latticeai/services/search_service.py +37 -795
- package/latticeai/services/tool_dispatch.py +30 -386
- package/latticeai/services/voice_capture.py +13 -107
- package/latticeai/tools/__init__.py +22 -49
- package/latticeai/tools/commands.py +0 -163
- package/latticeai/tools/computer.py +0 -39
- package/latticeai/tools/documents.py +1 -134
- package/latticeai/tools/filesystem.py +1 -247
- package/latticeai/tools/knowledge.py +1 -52
- package/latticeai/tools/local_files.py +0 -20
- package/latticeai/worker_app.py +75 -0
- package/package.json +2 -3
- package/requirements.txt +0 -5
- package/scripts/agent_eval.py +16 -28
- package/scripts/brain_quality_eval.py +20 -183
- package/scripts/bump_version.py +5 -5
- package/scripts/check_current_release_docs.mjs +7 -4
- package/scripts/check_openapi_drift.mjs +10 -0
- package/scripts/check_server_i18n.mjs +2 -18
- package/scripts/compose_openapi.py +377 -0
- package/scripts/export_openapi.py +32 -4
- package/scripts/gen_messages_catalog_fixture.py +302 -0
- package/scripts/gen_openapi_fragments.py +365 -0
- package/scripts/gen_redact_fixture.py +196 -0
- package/scripts/gen_worker_allowlist_fixture.py +115 -0
- package/scripts/generate_agent_parity_fixtures.py +33 -14
- package/scripts/openapi_route_families.json +2161 -0
- package/scripts/release_screen_claims.json +54 -0
- package/scripts/run_integration_tests.mjs +134 -29
- package/scripts/run_sidecar_e2e.mjs +104 -11
- package/scripts/wheel_smoke.py +32 -22
- package/src-tauri/Cargo.lock +375 -9
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/backend.rs +151 -45
- package/src-tauri/src/main.rs +18 -18
- package/src-tauri/src/topology.rs +58 -88
- package/src-tauri/tauri.conf.json +1 -2
- package/static/app/asset-manifest.json +41 -41
- package/static/app/assets/{Act-DcQizkl1.js → Act-BPcVAbOL.js} +1 -1
- package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
- package/static/app/assets/Capture-BsTokYkk.js +1 -0
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
- package/static/app/assets/System-CMHSO9qM.js +1 -0
- package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
- package/static/app/assets/circle-check-DruOxB-4.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
- package/static/app/assets/index-D9x-kSNy.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
- package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
- package/static/app/assets/search-CT9aho2j.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.js} +1 -1
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/archive.py +0 -522
- package/lattice_brain/context.py +0 -325
- package/lattice_brain/conversations.py +0 -382
- package/lattice_brain/core.py +0 -82
- package/lattice_brain/graph/_kg_contract.py +0 -249
- package/lattice_brain/graph/curator.py +0 -676
- package/lattice_brain/graph/discovery.py +0 -595
- package/lattice_brain/graph/discovery_index/__init__.py +0 -35
- package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
- package/lattice_brain/graph/discovery_index/extract.py +0 -137
- package/lattice_brain/graph/discovery_index/scan.py +0 -411
- package/lattice_brain/graph/discovery_index/upsert.py +0 -495
- package/lattice_brain/graph/documents.py +0 -380
- package/lattice_brain/graph/fusion.py +0 -395
- package/lattice_brain/graph/identity.py +0 -175
- package/lattice_brain/graph/image_vectors.py +0 -230
- package/lattice_brain/graph/ingest.py +0 -829
- package/lattice_brain/graph/network.py +0 -205
- package/lattice_brain/graph/proactive.py +0 -724
- package/lattice_brain/graph/projection/__init__.py +0 -42
- package/lattice_brain/graph/projection/curation.py +0 -500
- package/lattice_brain/graph/projection/v2_schema.py +0 -518
- package/lattice_brain/graph/provenance.py +0 -524
- package/lattice_brain/graph/rerank.py +0 -163
- package/lattice_brain/graph/retrieval/__init__.py +0 -54
- package/lattice_brain/graph/retrieval/context.py +0 -197
- package/lattice_brain/graph/retrieval/graph_view.py +0 -319
- package/lattice_brain/graph/retrieval/hybrid.py +0 -488
- package/lattice_brain/graph/retrieval/maintenance.py +0 -121
- package/lattice_brain/graph/retrieval/signals.py +0 -95
- package/lattice_brain/graph/retrieval_docgen.py +0 -253
- package/lattice_brain/graph/retrieval_policy.py +0 -180
- package/lattice_brain/graph/retrieval_reads.py +0 -769
- package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
- package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
- package/lattice_brain/graph/retrieval_vector/search.py +0 -560
- package/lattice_brain/graph/retrieval_vector/status.py +0 -374
- package/lattice_brain/graph/schema.py +0 -792
- package/lattice_brain/graph/store.py +0 -268
- package/lattice_brain/graph/vector_index/__init__.py +0 -85
- package/lattice_brain/graph/vector_index/base.py +0 -167
- package/lattice_brain/graph/vector_index/brute_force.py +0 -110
- package/lattice_brain/graph/vector_index/hnsw.py +0 -290
- package/lattice_brain/graph/vector_index/jobs.py +0 -287
- package/lattice_brain/graph/vector_index/quantized.py +0 -148
- package/lattice_brain/graph/vector_index/selector.py +0 -161
- package/lattice_brain/graph/write_master.py +0 -308
- package/lattice_brain/ingestion/_contract.py +0 -90
- package/lattice_brain/ingestion/folder_scan.py +0 -57
- package/lattice_brain/ingestion/folders.py +0 -258
- package/lattice_brain/ingestion/jobs_api.py +0 -107
- package/lattice_brain/ingestion/routing.py +0 -295
- package/lattice_brain/ingestion_jobs.py +0 -380
- package/lattice_brain/memory.py +0 -75
- package/lattice_brain/portability/__init__.py +0 -90
- package/lattice_brain/portability/_contract.py +0 -42
- package/lattice_brain/portability/backups.py +0 -338
- package/lattice_brain/portability/bundles.py +0 -136
- package/lattice_brain/portability/constants.py +0 -93
- package/lattice_brain/portability/fsops.py +0 -138
- package/lattice_brain/portability/service.py +0 -41
- package/lattice_brain/portability/sharing.py +0 -710
- package/lattice_brain/quality.py +0 -543
- package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
- package/lattice_brain/runtime/agent_runtime.py +0 -859
- package/lattice_brain/runtime/contracts.py +0 -460
- package/lattice_brain/runtime/multi_agent.py +0 -942
- package/lattice_brain/runtime/statuses.py +0 -10
- package/lattice_brain/sealed_box.py +0 -240
- package/lattice_brain/self_model.py +0 -675
- package/lattice_brain/sensitivity.py +0 -94
- package/lattice_brain/storage/__init__.py +0 -22
- package/lattice_brain/storage/base.py +0 -100
- package/lattice_brain/storage/docker.py +0 -105
- package/lattice_brain/storage/factory.py +0 -31
- package/lattice_brain/storage/migration.py +0 -191
- package/lattice_brain/storage/postgres.py +0 -123
- package/lattice_brain/storage/sqlite.py +0 -143
- package/lattice_brain/synthesis.py +0 -824
- package/lattice_brain/workflow.py +0 -497
- package/latticeai/api/admin.py +0 -471
- package/latticeai/api/agent_registry.py +0 -105
- package/latticeai/api/agents.py +0 -228
- package/latticeai/api/auth.py +0 -383
- package/latticeai/api/automation_intelligence.py +0 -401
- package/latticeai/api/brain_intelligence.py +0 -199
- package/latticeai/api/browser.py +0 -493
- package/latticeai/api/change_proposals.py +0 -89
- package/latticeai/api/chat.py +0 -572
- package/latticeai/api/chat_agent_http.py +0 -892
- package/latticeai/api/chat_contracts.py +0 -73
- package/latticeai/api/chat_documents.py +0 -276
- package/latticeai/api/chat_helpers.py +0 -460
- package/latticeai/api/chat_history.py +0 -101
- package/latticeai/api/chat_hybrid.py +0 -113
- package/latticeai/api/chat_intents.py +0 -646
- package/latticeai/api/chat_stream.py +0 -216
- package/latticeai/api/chronicle.py +0 -63
- package/latticeai/api/command_center.py +0 -51
- package/latticeai/api/computer_use.py +0 -474
- package/latticeai/api/evidence_actions.py +0 -48
- package/latticeai/api/features.py +0 -70
- package/latticeai/api/funnel_metrics.py +0 -31
- package/latticeai/api/garden.py +0 -34
- package/latticeai/api/hooks.py +0 -165
- package/latticeai/api/index_jobs.py +0 -145
- package/latticeai/api/invitations.py +0 -100
- package/latticeai/api/knowledge_graph.py +0 -536
- package/latticeai/api/marketplace.py +0 -105
- package/latticeai/api/mcp.py +0 -482
- package/latticeai/api/memory.py +0 -270
- package/latticeai/api/network.py +0 -81
- package/latticeai/api/network_boundary.py +0 -225
- package/latticeai/api/permission_mode.py +0 -61
- package/latticeai/api/permissions.py +0 -436
- package/latticeai/api/plugins.py +0 -126
- package/latticeai/api/portability.py +0 -391
- package/latticeai/api/project_sessions.py +0 -114
- package/latticeai/api/realtime.py +0 -118
- package/latticeai/api/review_queue.py +0 -364
- package/latticeai/api/security_dashboard.py +0 -604
- package/latticeai/api/setup.py +0 -319
- package/latticeai/api/static_routes.py +0 -354
- package/latticeai/api/ui_redirects.py +0 -26
- package/latticeai/api/workflow_designer.py +0 -394
- package/latticeai/api/workspace.py +0 -856
- package/latticeai/api/workspace_scope.py +0 -125
- package/latticeai/core/agent/__init__.py +0 -93
- package/latticeai/core/agent/_contract.py +0 -79
- package/latticeai/core/agent/context.py +0 -57
- package/latticeai/core/agent/deps.py +0 -125
- package/latticeai/core/agent/execution.py +0 -622
- package/latticeai/core/agent/planning.py +0 -145
- package/latticeai/core/agent/recovery.py +0 -157
- package/latticeai/core/agent/runtime.py +0 -210
- package/latticeai/core/agent/verification.py +0 -231
- package/latticeai/core/agent_eval.py +0 -739
- package/latticeai/core/agent_helpers.py +0 -493
- package/latticeai/core/agent_profiles.py +0 -110
- package/latticeai/core/agent_prompts.py +0 -171
- package/latticeai/core/agent_registry.py +0 -232
- package/latticeai/core/agent_state.py +0 -41
- package/latticeai/core/agent_trace.py +0 -104
- package/latticeai/core/artifact_ledger.py +0 -109
- package/latticeai/core/audit.py +0 -260
- package/latticeai/core/builtin_hooks.py +0 -105
- package/latticeai/core/context_builder.py +0 -394
- package/latticeai/core/document_generator.py +0 -103
- package/latticeai/core/enterprise.py +0 -154
- package/latticeai/core/enterprise_admin.py +0 -158
- package/latticeai/core/file_generation/__init__.py +0 -115
- package/latticeai/core/file_generation/bundles.py +0 -76
- package/latticeai/core/file_generation/extraction.py +0 -154
- package/latticeai/core/file_generation/inference.py +0 -235
- package/latticeai/core/file_generation/orchestration.py +0 -152
- package/latticeai/core/file_generation/prompting.py +0 -117
- package/latticeai/core/file_generation/repair.py +0 -114
- package/latticeai/core/file_generation/sanitize.py +0 -61
- package/latticeai/core/file_generation/validation.py +0 -201
- package/latticeai/core/invitations.py +0 -132
- package/latticeai/core/legacy_compatibility.py +0 -243
- package/latticeai/core/logging_safety.py +0 -46
- package/latticeai/core/marketplace.py +0 -293
- package/latticeai/core/mcp_catalog.py +0 -452
- package/latticeai/core/mcp_registry.py +0 -506
- package/latticeai/core/network_boundary.py +0 -168
- package/latticeai/core/oidc.py +0 -208
- package/latticeai/core/plugins.py +0 -432
- package/latticeai/core/product_hardening.py +0 -218
- package/latticeai/core/project_sessions.py +0 -337
- package/latticeai/core/realtime.py +0 -238
- package/latticeai/core/run_explain.py +0 -426
- package/latticeai/core/run_store.py +0 -252
- package/latticeai/core/timezones.py +0 -80
- package/latticeai/core/workspace_computer_memory.py +0 -84
- package/latticeai/core/workspace_graph_trace.py +0 -155
- package/latticeai/core/workspace_indexing.py +0 -102
- package/latticeai/core/workspace_memory.py +0 -77
- package/latticeai/core/workspace_onboarding.py +0 -104
- package/latticeai/core/workspace_os.py +0 -978
- package/latticeai/core/workspace_os_constants.py +0 -126
- package/latticeai/core/workspace_os_state.py +0 -180
- package/latticeai/core/workspace_os_utils.py +0 -103
- package/latticeai/core/workspace_permissions.py +0 -101
- package/latticeai/core/workspace_plugins.py +0 -97
- package/latticeai/core/workspace_relationships.py +0 -99
- package/latticeai/core/workspace_reorganization.py +0 -335
- package/latticeai/core/workspace_review_items.py +0 -112
- package/latticeai/core/workspace_runs.py +0 -726
- package/latticeai/core/workspace_skills.py +0 -109
- package/latticeai/core/workspace_snapshots.py +0 -198
- package/latticeai/core/workspace_timeline.py +0 -110
- package/latticeai/integrations/__init__.py +0 -0
- package/latticeai/integrations/telegram_bot/__init__.py +0 -123
- package/latticeai/integrations/telegram_bot/__main__.py +0 -17
- package/latticeai/integrations/telegram_bot/config.py +0 -86
- package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
- package/latticeai/integrations/telegram_bot/flows.py +0 -478
- package/latticeai/integrations/telegram_bot/helpers.py +0 -322
- package/latticeai/integrations/telegram_bot/screens.py +0 -394
- package/latticeai/runtime/audit_runtime.py +0 -76
- package/latticeai/runtime/automation_runtime.py +0 -81
- package/latticeai/runtime/chat_wiring.py +0 -141
- package/latticeai/runtime/context_runtime.py +0 -66
- package/latticeai/runtime/feature_toggle_wiring.py +0 -157
- package/latticeai/runtime/history_runtime.py +0 -163
- package/latticeai/runtime/history_writer.py +0 -138
- package/latticeai/runtime/hooks_runtime.py +0 -77
- package/latticeai/runtime/model_wiring.py +0 -68
- package/latticeai/runtime/namespace_runtime.py +0 -163
- package/latticeai/runtime/network_boundary_wiring.py +0 -117
- package/latticeai/runtime/network_config_runtime.py +0 -56
- package/latticeai/runtime/permission_mode_wiring.py +0 -112
- package/latticeai/runtime/persistence_runtime.py +0 -159
- package/latticeai/runtime/platform_runtime_wiring.py +0 -89
- package/latticeai/runtime/review_wiring.py +0 -42
- package/latticeai/runtime/router_registration.py +0 -693
- package/latticeai/runtime/service_singletons.py +0 -55
- package/latticeai/runtime/sso_config_runtime.py +0 -128
- package/latticeai/runtime/user_key_runtime.py +0 -106
- package/latticeai/runtime/web_runtime.py +0 -92
- package/latticeai/server_app.py +0 -51
- package/latticeai/services/app_context.py +0 -130
- package/latticeai/services/automation_execution.py +0 -266
- package/latticeai/services/automation_intelligence.py +0 -614
- package/latticeai/services/brain_automation.py +0 -191
- package/latticeai/services/brain_intelligence/__init__.py +0 -58
- package/latticeai/services/brain_intelligence/_contract.py +0 -71
- package/latticeai/services/brain_intelligence/consistency.py +0 -193
- package/latticeai/services/brain_intelligence/constants.py +0 -47
- package/latticeai/services/brain_intelligence/digest.py +0 -258
- package/latticeai/services/brain_intelligence/health.py +0 -331
- package/latticeai/services/brain_intelligence/proposals.py +0 -259
- package/latticeai/services/brain_intelligence/sampling.py +0 -84
- package/latticeai/services/brain_intelligence/service.py +0 -48
- package/latticeai/services/change_proposals.py +0 -471
- package/latticeai/services/chat_service.py +0 -243
- package/latticeai/services/chronicle.py +0 -555
- package/latticeai/services/cloud_egress_audit.py +0 -85
- package/latticeai/services/cloud_extraction.py +0 -129
- package/latticeai/services/cloud_streaming.py +0 -268
- package/latticeai/services/cloud_token_guard.py +0 -84
- package/latticeai/services/command_center.py +0 -548
- package/latticeai/services/evidence_actions.py +0 -258
- package/latticeai/services/feature_toggles.py +0 -502
- package/latticeai/services/folder_watch.py +0 -520
- package/latticeai/services/funnel_metrics.py +0 -307
- package/latticeai/services/hybrid_chat.py +0 -316
- package/latticeai/services/hybrid_context.py +0 -228
- package/latticeai/services/hybrid_policy.py +0 -129
- package/latticeai/services/interop_bridges.py +0 -978
- package/latticeai/services/local_knowledge.py +0 -465
- package/latticeai/services/memory_service/__init__.py +0 -52
- package/latticeai/services/memory_service/_contract.py +0 -100
- package/latticeai/services/memory_service/brief.py +0 -431
- package/latticeai/services/memory_service/constants.py +0 -57
- package/latticeai/services/memory_service/maintenance.py +0 -138
- package/latticeai/services/memory_service/manager.py +0 -186
- package/latticeai/services/memory_service/proof.py +0 -136
- package/latticeai/services/memory_service/recall.py +0 -225
- package/latticeai/services/memory_service/service.py +0 -48
- package/latticeai/services/memory_service/stores.py +0 -110
- package/latticeai/services/mode_store.py +0 -132
- package/latticeai/services/model_recommendation.py +0 -224
- package/latticeai/services/model_runtime/cloud.py +0 -87
- package/latticeai/services/network_boundary_service.py +0 -117
- package/latticeai/services/obsidian_bridge.py +0 -609
- package/latticeai/services/openai_compatible_adapter.py +0 -101
- package/latticeai/services/permission_mode_service.py +0 -122
- package/latticeai/services/platform_runtime.py +0 -366
- package/latticeai/services/review_queue.py +0 -380
- package/latticeai/services/router_context.py +0 -59
- package/latticeai/services/run_executor.py +0 -387
- package/latticeai/services/self_model_service.py +0 -171
- package/latticeai/services/setup_detection.py +0 -147
- package/latticeai/services/triggers.py +0 -378
- package/latticeai/services/upload_service.py +0 -172
- package/latticeai/services/workspace_service.py +0 -165
- package/latticeai/setup/__init__.py +0 -25
- package/latticeai/setup/auto_setup.py +0 -846
- package/latticeai/setup/demo_corpus.py +0 -98
- package/latticeai/setup/wizard/__init__.py +0 -126
- package/latticeai/setup/wizard/catalog.py +0 -175
- package/latticeai/setup/wizard/detect.py +0 -302
- package/latticeai/setup/wizard/install.py +0 -348
- package/latticeai/setup/wizard/paths.py +0 -165
- package/latticeai/setup/wizard/plans.py +0 -74
- package/latticeai/setup/wizard/recommend.py +0 -320
- package/scripts/bench_agent_smoke.py +0 -409
- package/scripts/bench_models.py +0 -540
- package/scripts/bench_vector_index.py +0 -295
- package/scripts/funnel_soft_gate.py +0 -192
- package/scripts/generate_agent_loop_fixtures.py +0 -994
- package/scripts/generate_rust_parity_fixtures.py +0 -908
- package/scripts/migrate_brain_storage.py +0 -57
- package/scripts/parity_fixture_corpus_context.py +0 -162
- package/scripts/parity_fixture_corpus_docgen.py +0 -341
- package/scripts/profile_kg.py +0 -355
- package/server.py +0 -30
- package/static/app/assets/AdminConsole-cf4npybT.js +0 -1
- package/static/app/assets/Capture-DiQ219jW.js +0 -1
- package/static/app/assets/System-CieofHQa.js +0 -1
- package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
- package/static/app/assets/circle-check-qqLug9nU.js +0 -1
- package/static/app/assets/index-DxmOfNRi.css +0 -2
- package/static/app/assets/primitives-SNp0LRJz.js +0 -1
- package/static/app/assets/search-BcHqkjoy.js +0 -1
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
"""Knowledge graph page and API routes.
|
|
2
|
-
|
|
3
|
-
Relocated from the root ``knowledge_graph_api.py`` in v4 (T2); the root module
|
|
4
|
-
remains as a deprecation shim. Route paths and response shapes are frozen by
|
|
5
|
-
``tests/unit/test_knowledge_graph_router_parity.py`` — the ``/knowledge-graph/*``
|
|
6
|
-
data endpoints back the v3 SPA (Files, Hybrid Search, graph explorer).
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Any, Callable, Dict, List, Optional
|
|
12
|
-
|
|
13
|
-
from fastapi import APIRouter, HTTPException, Request
|
|
14
|
-
from pydantic import BaseModel
|
|
15
|
-
|
|
16
|
-
from lattice_brain.ingestion import IngestionItem
|
|
17
|
-
from latticeai.api.ui_redirects import app_redirect
|
|
18
|
-
from latticeai.api.workspace_scope import (
|
|
19
|
-
resolve_workspace_scope,
|
|
20
|
-
workspace_scope_from_request,
|
|
21
|
-
)
|
|
22
|
-
from latticeai.core.messages import http_error, resolve_language
|
|
23
|
-
from latticeai.core.quiet import quiet
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class KnowledgeGraphIngestRequest(BaseModel):
|
|
27
|
-
type: str
|
|
28
|
-
content: str = ""
|
|
29
|
-
role: Optional[str] = None
|
|
30
|
-
title: Optional[str] = None
|
|
31
|
-
source: Optional[str] = None
|
|
32
|
-
conversation_id: Optional[str] = None
|
|
33
|
-
user_email: Optional[str] = None
|
|
34
|
-
user_nickname: Optional[str] = None
|
|
35
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class CurateNoiseRequest(BaseModel):
|
|
39
|
-
# Dry-run by default: the job *reports* removals until explicitly applied.
|
|
40
|
-
dry_run: bool = True
|
|
41
|
-
max_df_ratio: float = 0.8
|
|
42
|
-
min_doc_frequency: int = 1
|
|
43
|
-
min_corpus_docs: int = 5
|
|
44
|
-
normalize_verbs: bool = True
|
|
45
|
-
max_removals: int = 200
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class PromotionActionRequest(BaseModel):
|
|
49
|
-
# None applies/rejects every pending promotion; otherwise only these ids.
|
|
50
|
-
ids: Optional[List[str]] = None
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# Kept as a module-level name because callers import it from here; the
|
|
54
|
-
# implementation now lives in the shared resolver.
|
|
55
|
-
_workspace_scope_from_request = workspace_scope_from_request
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _format_context(matches: list, limit: int) -> str:
|
|
59
|
-
"""Mirror ``KnowledgeGraphRetrievalMixin.context_for_query`` formatting for a
|
|
60
|
-
pre-filtered match list, so scoped callers get identical context lines minus
|
|
61
|
-
the rows they are not allowed to see."""
|
|
62
|
-
lines = []
|
|
63
|
-
for match in matches[:limit]:
|
|
64
|
-
meta = match.get("metadata") or {}
|
|
65
|
-
source = (
|
|
66
|
-
meta.get("relative_path")
|
|
67
|
-
or meta.get("filename")
|
|
68
|
-
or meta.get("conversation_id")
|
|
69
|
-
or meta.get("source")
|
|
70
|
-
or match.get("id")
|
|
71
|
-
)
|
|
72
|
-
summary = " ".join(str(match.get("summary") or "").split())[:700]
|
|
73
|
-
lines.append(f"- [{match.get('type')}] {match.get('title')} | source={source} | {summary}")
|
|
74
|
-
return "\n".join(lines)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _pipeline_stage_view(*, count: int, pending: int) -> Dict[str, Any]:
|
|
78
|
-
"""Derive a coherent journey-ribbon stage from count + pending.
|
|
79
|
-
|
|
80
|
-
Invariants (enforced for layout-rebuild Capture screen 11):
|
|
81
|
-
- ``pending > 0`` → ``working``
|
|
82
|
-
- ``pending == 0`` and ``count > 0`` → ``done`` (never ``waiting``)
|
|
83
|
-
- ``pending == 0`` and ``count == 0`` → ``waiting`` (nothing arrived yet)
|
|
84
|
-
"""
|
|
85
|
-
safe_count = max(0, int(count))
|
|
86
|
-
safe_pending = max(0, int(pending))
|
|
87
|
-
if safe_pending > 0:
|
|
88
|
-
status = "working"
|
|
89
|
-
elif safe_count > 0:
|
|
90
|
-
status = "done"
|
|
91
|
-
else:
|
|
92
|
-
status = "waiting"
|
|
93
|
-
return {"count": safe_count, "pending": safe_pending, "status": status}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def create_knowledge_graph_router(
|
|
97
|
-
*,
|
|
98
|
-
get_graph: Callable[[], Any],
|
|
99
|
-
require_graph: Callable[[], None],
|
|
100
|
-
require_user: Callable[[Request], str],
|
|
101
|
-
static_dir: Path,
|
|
102
|
-
require_admin: Optional[Callable[[Request], Any]] = None,
|
|
103
|
-
allowed_workspaces_for: Optional[Callable[[Optional[str]], Any]] = None,
|
|
104
|
-
ingestion_pipeline: Any = None,
|
|
105
|
-
workspace_service: Any = None,
|
|
106
|
-
) -> APIRouter:
|
|
107
|
-
router = APIRouter()
|
|
108
|
-
|
|
109
|
-
def graph():
|
|
110
|
-
require_graph()
|
|
111
|
-
return get_graph()
|
|
112
|
-
|
|
113
|
-
def _scoped(request: Request):
|
|
114
|
-
"""Authenticate the caller and resolve their allowed workspace set.
|
|
115
|
-
|
|
116
|
-
Returns ``(graph, allowed)``. ``allowed is None`` means no scoping
|
|
117
|
-
(single-user / no-auth mode); otherwise it is the set of workspace ids
|
|
118
|
-
the caller may read. Legacy-global rows are private by default in this
|
|
119
|
-
multi-user path; maintenance callers can opt in only through the store
|
|
120
|
-
API's explicit ``include_legacy_global=True`` argument.
|
|
121
|
-
"""
|
|
122
|
-
user = require_user(request)
|
|
123
|
-
allowed = None
|
|
124
|
-
if allowed_workspaces_for is not None and user:
|
|
125
|
-
allowed = allowed_workspaces_for(user)
|
|
126
|
-
return graph(), allowed
|
|
127
|
-
|
|
128
|
-
def _filter_scoped(kg: Any, items: Any, allowed: Any) -> list:
|
|
129
|
-
"""Apply the fail-closed v2 scope contract, including to old stores."""
|
|
130
|
-
|
|
131
|
-
candidates = list(items)
|
|
132
|
-
try:
|
|
133
|
-
return kg.filter_scoped_nodes(
|
|
134
|
-
candidates,
|
|
135
|
-
allowed,
|
|
136
|
-
include_legacy_global=False,
|
|
137
|
-
)
|
|
138
|
-
except TypeError:
|
|
139
|
-
# A pre-hardening store may still interpret missing ids as global.
|
|
140
|
-
# Resolve authoritative scopes directly and keep only known rows in
|
|
141
|
-
# an allowed workspace instead of invoking that legacy behavior.
|
|
142
|
-
scopes = kg.workspaces_of([item.get("id") for item in candidates])
|
|
143
|
-
allowed_ids = {str(item) for item in allowed if item}
|
|
144
|
-
return [
|
|
145
|
-
item
|
|
146
|
-
for item in candidates
|
|
147
|
-
if str(item.get("id") or "") in scopes
|
|
148
|
-
and scopes[str(item.get("id") or "")] is not None
|
|
149
|
-
and str(scopes[str(item.get("id") or "")]) in allowed_ids
|
|
150
|
-
]
|
|
151
|
-
|
|
152
|
-
def _write_workspace(request: Request, user: str) -> Optional[str]:
|
|
153
|
-
return resolve_workspace_scope(
|
|
154
|
-
request,
|
|
155
|
-
user=user,
|
|
156
|
-
workspace_service=workspace_service,
|
|
157
|
-
write=True,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
def _scoped_stats(request: Request) -> Dict[str, Any]:
|
|
161
|
-
"""Store statistics restricted to what the caller may read.
|
|
162
|
-
|
|
163
|
-
``stats()`` counted every row in the database, so a member of one
|
|
164
|
-
organization workspace could read another's node/edge/document volume
|
|
165
|
-
off a "harmless" metrics endpoint. Unscoped mode (single-user / no
|
|
166
|
-
auth) still gets the whole-store counts, which is the same number it
|
|
167
|
-
always was.
|
|
168
|
-
"""
|
|
169
|
-
kg, allowed = _scoped(request)
|
|
170
|
-
if allowed is None:
|
|
171
|
-
return dict(kg.stats())
|
|
172
|
-
try:
|
|
173
|
-
return dict(
|
|
174
|
-
kg.stats(allowed_workspaces=allowed, include_legacy_global=False)
|
|
175
|
-
)
|
|
176
|
-
except TypeError:
|
|
177
|
-
# A store predating scoped stats cannot answer the scoped
|
|
178
|
-
# question. Keep the response shape and empty the aggregates it
|
|
179
|
-
# could not restrict, rather than leaking whole-store totals.
|
|
180
|
-
payload = dict(kg.stats())
|
|
181
|
-
empty: Dict[str, Any] = {
|
|
182
|
-
"nodes": {},
|
|
183
|
-
"edges": {},
|
|
184
|
-
"local_sources": 0,
|
|
185
|
-
"local_file_status": {},
|
|
186
|
-
}
|
|
187
|
-
payload.update(
|
|
188
|
-
{key: value for key, value in empty.items() if key in payload}
|
|
189
|
-
)
|
|
190
|
-
return payload
|
|
191
|
-
|
|
192
|
-
@router.get("/graph")
|
|
193
|
-
async def knowledge_graph_page(request: Request):
|
|
194
|
-
"""Serve the interactive knowledge graph canvas UI."""
|
|
195
|
-
graph()
|
|
196
|
-
require_user(request)
|
|
197
|
-
return app_redirect("knowledge-graph", request)
|
|
198
|
-
|
|
199
|
-
@router.get("/knowledge-graph")
|
|
200
|
-
async def knowledge_graph_legacy_page(request: Request):
|
|
201
|
-
"""Backward-compatible route for the graph page."""
|
|
202
|
-
graph()
|
|
203
|
-
require_user(request)
|
|
204
|
-
return app_redirect("knowledge-graph", request)
|
|
205
|
-
|
|
206
|
-
@router.post("/knowledge-graph/curate")
|
|
207
|
-
async def knowledge_graph_curate(request: Request):
|
|
208
|
-
# Curation rewrites the shared graph and is therefore an administrative
|
|
209
|
-
# operation whenever role-based authentication is configured. The
|
|
210
|
-
# fallback preserves the standalone/local router contract used by
|
|
211
|
-
# embedders that only provide ``require_user``.
|
|
212
|
-
(require_admin or require_user)(request)
|
|
213
|
-
return graph().curate()
|
|
214
|
-
|
|
215
|
-
@router.post("/knowledge-graph/curate/noise")
|
|
216
|
-
async def knowledge_graph_curate_noise(req: CurateNoiseRequest, request: Request):
|
|
217
|
-
"""Noise-reduction curation job (backlog #10).
|
|
218
|
-
|
|
219
|
-
Dry-run by default: reports which heuristic concept nodes would be
|
|
220
|
-
removed (low IDF / below the frequency floor) and which relation
|
|
221
|
-
verbs would be normalized, without changing the graph. Set
|
|
222
|
-
``dry_run=false`` to apply. User-created nodes are never removed.
|
|
223
|
-
Administrative like ``/knowledge-graph/curate`` when roles exist.
|
|
224
|
-
"""
|
|
225
|
-
(require_admin or require_user)(request)
|
|
226
|
-
return graph().curate_noise(
|
|
227
|
-
dry_run=req.dry_run,
|
|
228
|
-
max_df_ratio=req.max_df_ratio,
|
|
229
|
-
min_doc_frequency=req.min_doc_frequency,
|
|
230
|
-
min_corpus_docs=req.min_corpus_docs,
|
|
231
|
-
normalize_verbs=req.normalize_verbs,
|
|
232
|
-
max_removals=req.max_removals,
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
@router.get("/knowledge-graph/promotions")
|
|
236
|
-
async def knowledge_graph_promotions(request: Request):
|
|
237
|
-
"""Pending curator promotions awaiting human review (review Wave 4).
|
|
238
|
-
|
|
239
|
-
Populated when ``curate()`` runs in review mode (explicit
|
|
240
|
-
``review_mode=True`` or the LATTICEAI_GRAPH_PROMOTION_REVIEW env
|
|
241
|
-
opt-in). Administrative like ``/knowledge-graph/curate``: the queue
|
|
242
|
-
governs the shared graph.
|
|
243
|
-
"""
|
|
244
|
-
(require_admin or require_user)(request)
|
|
245
|
-
pending = graph().pending_promotions()
|
|
246
|
-
return {"pending": pending, "total": len(pending)}
|
|
247
|
-
|
|
248
|
-
@router.post("/knowledge-graph/promotions/apply")
|
|
249
|
-
async def knowledge_graph_promotions_apply(
|
|
250
|
-
req: PromotionActionRequest, request: Request
|
|
251
|
-
):
|
|
252
|
-
"""Apply pending promotions (all when ``ids`` is omitted)."""
|
|
253
|
-
(require_admin or require_user)(request)
|
|
254
|
-
return graph().apply_pending_promotions(ids=req.ids)
|
|
255
|
-
|
|
256
|
-
@router.post("/knowledge-graph/promotions/reject")
|
|
257
|
-
async def knowledge_graph_promotions_reject(
|
|
258
|
-
req: PromotionActionRequest, request: Request
|
|
259
|
-
):
|
|
260
|
-
"""Reject pending promotions without writing (all when ``ids`` omitted)."""
|
|
261
|
-
(require_admin or require_user)(request)
|
|
262
|
-
return graph().reject_pending_promotions(ids=req.ids)
|
|
263
|
-
|
|
264
|
-
@router.get("/knowledge-graph/provenance/coverage")
|
|
265
|
-
async def knowledge_graph_provenance_coverage(request: Request):
|
|
266
|
-
require_user(request)
|
|
267
|
-
return graph().provenance_coverage()
|
|
268
|
-
|
|
269
|
-
@router.get("/knowledge-graph/stats")
|
|
270
|
-
async def knowledge_graph_stats(request: Request):
|
|
271
|
-
return _scoped_stats(request)
|
|
272
|
-
|
|
273
|
-
@router.get("/knowledge-graph/pipeline/status")
|
|
274
|
-
async def knowledge_graph_pipeline_status(request: Request):
|
|
275
|
-
"""Per-stage counts + status for the Capture 3-step journey ribbon.
|
|
276
|
-
|
|
277
|
-
Aggregates existing store data only — no schema change. Top-level
|
|
278
|
-
``received`` / ``extracted`` / ``connected`` stay for simple clients;
|
|
279
|
-
``stages`` is the single source of truth for count, pending, and
|
|
280
|
-
status (``done`` / ``working`` / ``waiting``). Keys are omitted when
|
|
281
|
-
a value cannot be computed (never faked as 0 when the underlying
|
|
282
|
-
source is unavailable).
|
|
283
|
-
"""
|
|
284
|
-
kg, allowed = _scoped(request)
|
|
285
|
-
received: Optional[int] = None
|
|
286
|
-
extracted: Optional[int] = None
|
|
287
|
-
connected: Optional[int] = None
|
|
288
|
-
index_pending: Optional[int] = None
|
|
289
|
-
|
|
290
|
-
try:
|
|
291
|
-
docs_payload = kg.list_documents(10_000)
|
|
292
|
-
documents = list(docs_payload.get("documents") or [])
|
|
293
|
-
if allowed is not None:
|
|
294
|
-
documents = _filter_scoped(kg, documents, allowed)
|
|
295
|
-
received = len(documents)
|
|
296
|
-
extracted = sum(
|
|
297
|
-
1
|
|
298
|
-
for doc in documents
|
|
299
|
-
if doc.get("indexed") or int(doc.get("chunks") or 0) > 0
|
|
300
|
-
)
|
|
301
|
-
except Exception:
|
|
302
|
-
quiet()
|
|
303
|
-
|
|
304
|
-
try:
|
|
305
|
-
stats = _scoped_stats(request)
|
|
306
|
-
edges = stats.get("edges")
|
|
307
|
-
if isinstance(edges, dict):
|
|
308
|
-
connected = sum(int(value or 0) for value in edges.values())
|
|
309
|
-
elif isinstance(edges, (int, float)) and not isinstance(edges, bool):
|
|
310
|
-
connected = int(edges)
|
|
311
|
-
elif isinstance(stats.get("v2"), dict):
|
|
312
|
-
v2_edges = stats["v2"].get("edges")
|
|
313
|
-
if isinstance(v2_edges, (int, float)) and not isinstance(v2_edges, bool):
|
|
314
|
-
connected = int(v2_edges)
|
|
315
|
-
if received is None:
|
|
316
|
-
nodes = stats.get("nodes")
|
|
317
|
-
if isinstance(nodes, dict):
|
|
318
|
-
received = sum(
|
|
319
|
-
int(value or 0)
|
|
320
|
-
for key, value in nodes.items()
|
|
321
|
-
if str(key).lower() != "chunk"
|
|
322
|
-
)
|
|
323
|
-
elif isinstance(nodes, (int, float)) and not isinstance(nodes, bool):
|
|
324
|
-
received = int(nodes)
|
|
325
|
-
except Exception:
|
|
326
|
-
quiet()
|
|
327
|
-
|
|
328
|
-
index_fn = getattr(kg, "index_status", None)
|
|
329
|
-
if callable(index_fn):
|
|
330
|
-
try:
|
|
331
|
-
index = index_fn() or {}
|
|
332
|
-
if received is None and index.get("source_items") is not None:
|
|
333
|
-
received = int(index.get("source_items") or 0)
|
|
334
|
-
if extracted is None and index.get("ready_items") is not None:
|
|
335
|
-
extracted = int(index.get("ready_items") or 0)
|
|
336
|
-
if index.get("pending_items") is not None:
|
|
337
|
-
index_pending = max(0, int(index.get("pending_items") or 0))
|
|
338
|
-
elif index.get("pending") is not None:
|
|
339
|
-
index_pending = max(0, int(index.get("pending") or 0))
|
|
340
|
-
except Exception:
|
|
341
|
-
quiet()
|
|
342
|
-
|
|
343
|
-
payload: Dict[str, Any] = {}
|
|
344
|
-
if received is not None:
|
|
345
|
-
payload["received"] = max(0, int(received))
|
|
346
|
-
if extracted is not None:
|
|
347
|
-
payload["extracted"] = max(0, int(extracted))
|
|
348
|
-
if connected is not None:
|
|
349
|
-
payload["connected"] = max(0, int(connected))
|
|
350
|
-
|
|
351
|
-
if payload:
|
|
352
|
-
stages: Dict[str, Dict[str, Any]] = {}
|
|
353
|
-
if "received" in payload:
|
|
354
|
-
# Once listed as a document it has been received — no backlog.
|
|
355
|
-
stages["received"] = _pipeline_stage_view(
|
|
356
|
-
count=payload["received"], pending=0
|
|
357
|
-
)
|
|
358
|
-
if "extracted" in payload:
|
|
359
|
-
# Prefer vector-index backlog; fall back to docs not yet chunked.
|
|
360
|
-
if index_pending is not None:
|
|
361
|
-
extract_pending = index_pending
|
|
362
|
-
elif "received" in payload:
|
|
363
|
-
extract_pending = max(
|
|
364
|
-
0, int(payload["received"]) - int(payload["extracted"])
|
|
365
|
-
)
|
|
366
|
-
else:
|
|
367
|
-
extract_pending = 0
|
|
368
|
-
stages["extracted"] = _pipeline_stage_view(
|
|
369
|
-
count=payload["extracted"], pending=extract_pending
|
|
370
|
-
)
|
|
371
|
-
if "connected" in payload:
|
|
372
|
-
# Connections are graph edges; backlog only when index reports
|
|
373
|
-
# pending items that still need linking (reuse extract backlog
|
|
374
|
-
# only when we have no better signal — 0 when graph is ready).
|
|
375
|
-
connect_pending = 0
|
|
376
|
-
if index_pending is not None and payload["connected"] == 0:
|
|
377
|
-
connect_pending = index_pending
|
|
378
|
-
stages["connected"] = _pipeline_stage_view(
|
|
379
|
-
count=payload["connected"], pending=connect_pending
|
|
380
|
-
)
|
|
381
|
-
payload["stages"] = stages
|
|
382
|
-
payload["updated_at"] = datetime.now().isoformat(timespec="seconds")
|
|
383
|
-
return payload
|
|
384
|
-
|
|
385
|
-
@router.get("/knowledge-graph/schema")
|
|
386
|
-
async def knowledge_graph_schema(request: Request):
|
|
387
|
-
stats = _scoped_stats(request)
|
|
388
|
-
return {
|
|
389
|
-
"legacy_schema_version": stats.get("schema_version"),
|
|
390
|
-
"v2_schema_available": stats.get("v2_schema_available"),
|
|
391
|
-
"v2": stats.get("v2"),
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
@router.get("/knowledge-graph/graph")
|
|
395
|
-
async def knowledge_graph_data(request: Request, limit: int = 300):
|
|
396
|
-
kg, allowed = _scoped(request)
|
|
397
|
-
if allowed is None:
|
|
398
|
-
return kg.graph(limit)
|
|
399
|
-
try:
|
|
400
|
-
return kg.graph(
|
|
401
|
-
limit,
|
|
402
|
-
allowed_workspaces=allowed,
|
|
403
|
-
include_legacy_global=False,
|
|
404
|
-
)
|
|
405
|
-
except TypeError:
|
|
406
|
-
payload = kg.graph(limit)
|
|
407
|
-
nodes = _filter_scoped(kg, payload.get("nodes", []), allowed)
|
|
408
|
-
kept = {node.get("id") for node in nodes}
|
|
409
|
-
return {
|
|
410
|
-
**payload,
|
|
411
|
-
"nodes": nodes,
|
|
412
|
-
"edges": [
|
|
413
|
-
edge
|
|
414
|
-
for edge in payload.get("edges", [])
|
|
415
|
-
if edge.get("from") in kept and edge.get("to") in kept
|
|
416
|
-
],
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
@router.get("/knowledge-graph/documents")
|
|
420
|
-
async def knowledge_graph_documents(request: Request, limit: int = 200):
|
|
421
|
-
"""Ingested documents (uploads + indexed local docs) with index state.
|
|
422
|
-
|
|
423
|
-
Backs the Files view so uploaded content is visible end-to-end:
|
|
424
|
-
upload → Files → Knowledge Graph → Hybrid Search → Chat.
|
|
425
|
-
"""
|
|
426
|
-
kg, allowed = _scoped(request)
|
|
427
|
-
payload = kg.list_documents(limit)
|
|
428
|
-
if allowed is not None:
|
|
429
|
-
documents = _filter_scoped(kg, payload.get("documents", []), allowed)
|
|
430
|
-
payload = {**payload, "documents": documents, "total": len(documents)}
|
|
431
|
-
return payload
|
|
432
|
-
|
|
433
|
-
@router.get("/knowledge-graph/search")
|
|
434
|
-
async def knowledge_graph_search(q: str, request: Request, limit: int = 30):
|
|
435
|
-
kg, allowed = _scoped(request)
|
|
436
|
-
if not q or not q.strip():
|
|
437
|
-
return {"query": q, "matches": []}
|
|
438
|
-
payload = kg.search(q, limit)
|
|
439
|
-
if allowed is not None:
|
|
440
|
-
payload = {
|
|
441
|
-
**payload,
|
|
442
|
-
"matches": _filter_scoped(kg, payload.get("matches", []), allowed),
|
|
443
|
-
}
|
|
444
|
-
return payload
|
|
445
|
-
|
|
446
|
-
@router.get("/knowledge-graph/context")
|
|
447
|
-
async def knowledge_graph_context(q: str, request: Request, limit: int = 6):
|
|
448
|
-
kg, allowed = _scoped(request)
|
|
449
|
-
if allowed is None:
|
|
450
|
-
return {"query": q, "context": kg.context_for_query(q, limit)}
|
|
451
|
-
# Scoped mode: derive context from scope-filtered search matches so the
|
|
452
|
-
# RAG context never carries content from workspaces the caller can't read.
|
|
453
|
-
matches = _filter_scoped(kg, kg.search(q, limit).get("matches", []), allowed)
|
|
454
|
-
return {"query": q, "context": _format_context(matches, limit)}
|
|
455
|
-
|
|
456
|
-
@router.get("/knowledge-graph/neighbors/{node_id:path}")
|
|
457
|
-
async def knowledge_graph_neighbors(node_id: str, request: Request):
|
|
458
|
-
kg, allowed = _scoped(request)
|
|
459
|
-
if not node_id:
|
|
460
|
-
raise http_error(400, "graph.node_id_required", resolve_language(request))
|
|
461
|
-
if allowed is not None and not _filter_scoped(kg, [{"id": node_id}], allowed):
|
|
462
|
-
raise http_error(404, "graph.node_not_found", resolve_language(request))
|
|
463
|
-
payload = kg.neighbors(node_id)
|
|
464
|
-
if allowed is not None:
|
|
465
|
-
neighbors = _filter_scoped(kg, payload.get("neighbors", []), allowed)
|
|
466
|
-
kept = {n.get("id") for n in neighbors}
|
|
467
|
-
edges = [
|
|
468
|
-
e for e in payload.get("edges", [])
|
|
469
|
-
if (e.get("from") == node_id or e.get("from") in kept)
|
|
470
|
-
and (e.get("to") == node_id or e.get("to") in kept)
|
|
471
|
-
]
|
|
472
|
-
payload = {**payload, "neighbors": neighbors, "edges": edges}
|
|
473
|
-
return payload
|
|
474
|
-
|
|
475
|
-
@router.post("/knowledge-graph/ingest")
|
|
476
|
-
async def knowledge_graph_ingest(req: KnowledgeGraphIngestRequest, request: Request):
|
|
477
|
-
current_user = require_user(request)
|
|
478
|
-
if current_user and req.user_email:
|
|
479
|
-
if current_user.strip().lower() != req.user_email.strip().lower():
|
|
480
|
-
raise http_error(403, "common.user_mismatch", resolve_language(request))
|
|
481
|
-
effective_user = current_user or req.user_email or None
|
|
482
|
-
kg = graph()
|
|
483
|
-
workspace_id = _write_workspace(request, current_user)
|
|
484
|
-
event_type = (req.type or "").strip().lower()
|
|
485
|
-
if event_type not in {"message", "ai_response", "note"}:
|
|
486
|
-
raise http_error(400, "graph.unsupported_type", resolve_language(request))
|
|
487
|
-
role = req.role or ("assistant" if event_type == "ai_response" else "user")
|
|
488
|
-
if ingestion_pipeline is not None:
|
|
489
|
-
source_type = "chat_message" if event_type in {"message", "ai_response"} else "note"
|
|
490
|
-
result = ingestion_pipeline.ingest(
|
|
491
|
-
IngestionItem(
|
|
492
|
-
source_type=source_type,
|
|
493
|
-
title=req.title,
|
|
494
|
-
text=req.content,
|
|
495
|
-
source_uri=req.source,
|
|
496
|
-
owner=effective_user,
|
|
497
|
-
workspace_id=workspace_id,
|
|
498
|
-
conversation_id=req.conversation_id,
|
|
499
|
-
metadata={
|
|
500
|
-
"type": req.type,
|
|
501
|
-
"role": role,
|
|
502
|
-
"source": req.source or "mcp",
|
|
503
|
-
"user_nickname": req.user_nickname,
|
|
504
|
-
"raw": {
|
|
505
|
-
"type": req.type,
|
|
506
|
-
"title": req.title,
|
|
507
|
-
"content": req.content,
|
|
508
|
-
"workspace_id": workspace_id,
|
|
509
|
-
"metadata": req.metadata or {},
|
|
510
|
-
},
|
|
511
|
-
**(req.metadata or {}),
|
|
512
|
-
},
|
|
513
|
-
),
|
|
514
|
-
user_email=effective_user,
|
|
515
|
-
)
|
|
516
|
-
if result.status != "ok":
|
|
517
|
-
raise HTTPException(status_code=500, detail=result.detail or result.status)
|
|
518
|
-
return result.as_dict()
|
|
519
|
-
return kg.ingest_message(
|
|
520
|
-
role,
|
|
521
|
-
req.content,
|
|
522
|
-
user_email=effective_user,
|
|
523
|
-
user_nickname=req.user_nickname,
|
|
524
|
-
source=req.source or "mcp",
|
|
525
|
-
conversation_id=req.conversation_id,
|
|
526
|
-
workspace_id=workspace_id,
|
|
527
|
-
raw={
|
|
528
|
-
"type": req.type,
|
|
529
|
-
"title": req.title,
|
|
530
|
-
"content": req.content,
|
|
531
|
-
"workspace_id": workspace_id,
|
|
532
|
-
"metadata": req.metadata or {},
|
|
533
|
-
},
|
|
534
|
-
)
|
|
535
|
-
|
|
536
|
-
return router
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
"""Marketplace foundation API (local templates only)."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any, Callable, Dict, Optional
|
|
6
|
-
|
|
7
|
-
from fastapi import APIRouter, HTTPException, Request
|
|
8
|
-
from pydantic import BaseModel
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TemplateImportRequest(BaseModel):
|
|
12
|
-
data: Dict[str, Any] = {}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TemplateInstallRequest(BaseModel):
|
|
16
|
-
data: Dict[str, Any] = {}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TemplateCloneRequest(BaseModel):
|
|
20
|
-
name: Optional[str] = None
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def create_marketplace_router(
|
|
24
|
-
*,
|
|
25
|
-
store,
|
|
26
|
-
catalog,
|
|
27
|
-
require_user: Callable[[Request], str],
|
|
28
|
-
gate_read: Callable[[Request], Optional[str]],
|
|
29
|
-
gate_write: Callable[[Request], Optional[str]],
|
|
30
|
-
workspace_graph: Callable[[], Any],
|
|
31
|
-
) -> APIRouter:
|
|
32
|
-
from latticeai.core.marketplace import MarketplaceError
|
|
33
|
-
|
|
34
|
-
router = APIRouter()
|
|
35
|
-
|
|
36
|
-
@router.get("/marketplace/templates")
|
|
37
|
-
async def list_templates(request: Request, kind: Optional[str] = None):
|
|
38
|
-
require_user(request)
|
|
39
|
-
gate_read(request)
|
|
40
|
-
try:
|
|
41
|
-
return catalog.list_templates(kind=kind)
|
|
42
|
-
except MarketplaceError as exc:
|
|
43
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
44
|
-
|
|
45
|
-
@router.get("/marketplace/templates/{kind}/{template_id}/export")
|
|
46
|
-
async def export_template(kind: str, template_id: str, request: Request):
|
|
47
|
-
require_user(request)
|
|
48
|
-
gate_read(request)
|
|
49
|
-
try:
|
|
50
|
-
return catalog.export_template(kind, template_id)
|
|
51
|
-
except MarketplaceError as exc:
|
|
52
|
-
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
53
|
-
|
|
54
|
-
@router.post("/marketplace/templates/import")
|
|
55
|
-
async def import_template(req: TemplateImportRequest, request: Request):
|
|
56
|
-
require_user(request)
|
|
57
|
-
gate_read(request)
|
|
58
|
-
try:
|
|
59
|
-
return {"template": catalog.import_template(req.data)}
|
|
60
|
-
except MarketplaceError as exc:
|
|
61
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
62
|
-
|
|
63
|
-
@router.post("/marketplace/templates/install")
|
|
64
|
-
async def install_template(req: TemplateInstallRequest, request: Request):
|
|
65
|
-
user = require_user(request)
|
|
66
|
-
scope = gate_write(request)
|
|
67
|
-
try:
|
|
68
|
-
installed = catalog.install_template(
|
|
69
|
-
req.data,
|
|
70
|
-
store=store,
|
|
71
|
-
user_email=user or None,
|
|
72
|
-
workspace_id=scope,
|
|
73
|
-
graph=workspace_graph(),
|
|
74
|
-
)
|
|
75
|
-
except MarketplaceError as exc:
|
|
76
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
77
|
-
return {"installed": installed}
|
|
78
|
-
|
|
79
|
-
@router.post("/marketplace/templates/{kind}/{template_id}/clone")
|
|
80
|
-
async def clone_template(kind: str, template_id: str, req: TemplateCloneRequest, request: Request):
|
|
81
|
-
require_user(request)
|
|
82
|
-
gate_read(request)
|
|
83
|
-
try:
|
|
84
|
-
return {"template": catalog.clone_template(kind, template_id, req.name)}
|
|
85
|
-
except MarketplaceError as exc:
|
|
86
|
-
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
87
|
-
|
|
88
|
-
@router.get("/marketplace/templates/registry")
|
|
89
|
-
async def template_registry(request: Request):
|
|
90
|
-
require_user(request)
|
|
91
|
-
scope = gate_read(request)
|
|
92
|
-
return {"registry": store.list_template_registry(workspace_id=scope)}
|
|
93
|
-
|
|
94
|
-
@router.get("/marketplace/interop/bridges")
|
|
95
|
-
async def list_interop_bridges(request: Request):
|
|
96
|
-
require_user(request)
|
|
97
|
-
gate_read(request)
|
|
98
|
-
bridges = catalog.list_templates(kind="ingestion_bridge")["templates"]
|
|
99
|
-
return {
|
|
100
|
-
"bridges": bridges,
|
|
101
|
-
"total": len(bridges),
|
|
102
|
-
"pipeline": "unified-ingestion",
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return router
|