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,315 +1,30 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""The multi-modal capability probe.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import platform
|
|
8
|
-
import time
|
|
9
|
-
import uuid
|
|
10
|
-
from datetime import datetime
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any, Dict, List, Optional
|
|
13
|
-
|
|
14
|
-
from fastapi import APIRouter, BackgroundTasks, HTTPException, Request
|
|
15
|
-
from fastapi.responses import FileResponse
|
|
16
|
-
from pydantic import BaseModel
|
|
17
|
-
|
|
18
|
-
from latticeai.api.knowledge_graph import create_knowledge_graph_router
|
|
19
|
-
from latticeai.core.messages import (
|
|
20
|
-
DEFAULT_LANGUAGE,
|
|
21
|
-
http_error,
|
|
22
|
-
resolve_language,
|
|
23
|
-
translate,
|
|
24
|
-
)
|
|
25
|
-
from latticeai.services.local_knowledge import create_local_knowledge_router
|
|
26
|
-
from latticeai.tools import local_list, local_read, local_write
|
|
27
|
-
|
|
28
|
-
try:
|
|
29
|
-
from latticeai import __version__ as _LATTICE_VERSION
|
|
30
|
-
except Exception: # pragma: no cover - defensive
|
|
31
|
-
_LATTICE_VERSION = "unknown"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class LocalAccessRequest(BaseModel):
|
|
35
|
-
path: str
|
|
36
|
-
approved: bool = False
|
|
37
|
-
approval_token: Optional[str] = None
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class LocalWriteRequest(BaseModel):
|
|
41
|
-
path: str
|
|
42
|
-
content: str
|
|
43
|
-
approved: bool = False
|
|
44
|
-
approval_token: Optional[str] = None
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class FolderIngestRequest(BaseModel):
|
|
48
|
-
path: str
|
|
49
|
-
recursive: bool = True
|
|
50
|
-
background: bool = False
|
|
51
|
-
workspace_id: Optional[str] = None
|
|
52
|
-
# Local filesystem reads follow the standard approval dance (same as
|
|
53
|
-
# /local/read and /knowledge-graph/local/index): the first call returns a
|
|
54
|
-
# permission_required payload with an approval token.
|
|
55
|
-
approved: bool = False
|
|
56
|
-
approval_token: Optional[str] = None
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class FolderWatchEnableRequest(BaseModel):
|
|
60
|
-
path: str
|
|
61
|
-
recursive: bool = True
|
|
62
|
-
workspace_id: Optional[str] = None
|
|
63
|
-
#: ``folder`` (default, unchanged) or ``vault`` — a watched external
|
|
64
|
-
#: Obsidian vault, which re-runs the whole bridge sync when it changes and
|
|
65
|
-
#: carries its own opt-in gate on top of this approval.
|
|
66
|
-
kind: str = "folder"
|
|
67
|
-
# Watching continuously reads local disk → same approval dance as
|
|
68
|
-
# /api/ingestion/folder. This is the explicit opt-in the review requires.
|
|
69
|
-
approved: bool = False
|
|
70
|
-
approval_token: Optional[str] = None
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class InteropIngestRequest(BaseModel):
|
|
74
|
-
"""Read one local interop source through the one ingestion gate (v11.2.0).
|
|
75
|
-
|
|
76
|
-
``source`` picks the bridge — ``notion`` (an export directory or ``.zip``),
|
|
77
|
-
``git`` (a local repository), ``mail`` (``.eml``/``.ics`` files or a folder
|
|
78
|
-
of them). Every one of them reads a path the user approves through the same
|
|
79
|
-
local-read dance as ``/api/ingestion/folder``, and ``dry_run`` reports what
|
|
80
|
-
a real run would touch without writing anything.
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
source: str
|
|
84
|
-
path: str
|
|
85
|
-
workspace_id: Optional[str] = None
|
|
86
|
-
dry_run: bool = False
|
|
87
|
-
#: Git only: how far back to read. Ignored by the other bridges.
|
|
88
|
-
max_commits: Optional[int] = None
|
|
89
|
-
approved: bool = False
|
|
90
|
-
approval_token: Optional[str] = None
|
|
3
|
+
This module was the local-file surface: the home-sandbox browser, the folder
|
|
4
|
+
ingest door, the watch registry, the Obsidian and interop bridges, the
|
|
5
|
+
background-job views and ``/local/serve``. Every one of those either writes or
|
|
6
|
+
reads state ``lattice-host`` owns now, so v11.6.0 keeps a single route.
|
|
91
7
|
|
|
8
|
+
``GET /api/ingestion/multimodal`` answers what this machine will actually do
|
|
9
|
+
with a picture, a recording or a video — the gates, the ports that resolved, and
|
|
10
|
+
which of the three reasons a video would be refused for. It reports capability,
|
|
11
|
+
not backlog: nothing here ingests, and the pipeline it asks holds no store.
|
|
12
|
+
"""
|
|
92
13
|
|
|
93
|
-
|
|
94
|
-
"""One-shot sync of an *external* Obsidian vault (v11.1.0).
|
|
14
|
+
from __future__ import annotations
|
|
95
15
|
|
|
96
|
-
|
|
97
|
-
approval dance as ``/api/ingestion/folder``. ``dry_run`` reports the note,
|
|
98
|
-
link, and tag counts a real run would touch without writing anything.
|
|
99
|
-
"""
|
|
16
|
+
from typing import Any, Callable
|
|
100
17
|
|
|
101
|
-
|
|
102
|
-
workspace_id: Optional[str] = None
|
|
103
|
-
dry_run: bool = False
|
|
104
|
-
approved: bool = False
|
|
105
|
-
approval_token: Optional[str] = None
|
|
18
|
+
from fastapi import APIRouter, Request
|
|
106
19
|
|
|
107
20
|
|
|
108
21
|
def create_local_files_router(
|
|
109
22
|
*,
|
|
110
|
-
require_user,
|
|
111
|
-
|
|
112
|
-
tool_response,
|
|
113
|
-
permission_gateway,
|
|
114
|
-
knowledge_graph,
|
|
115
|
-
require_graph,
|
|
116
|
-
static_dir: Path,
|
|
117
|
-
local_kg_watcher,
|
|
118
|
-
ingestion_pipeline=None,
|
|
119
|
-
hooks=None,
|
|
120
|
-
data_dir: Optional[Path] = None,
|
|
121
|
-
allowed_workspaces_for=None,
|
|
122
|
-
workspace_service=None,
|
|
123
|
-
folder_watch=None,
|
|
23
|
+
require_user: Callable[[Request], Any],
|
|
24
|
+
ingestion_pipeline: Any,
|
|
124
25
|
) -> APIRouter:
|
|
125
26
|
router = APIRouter()
|
|
126
27
|
|
|
127
|
-
# ── opt-in folder watch service (backlog #8) ──────────────────────────────
|
|
128
|
-
# Constructed here (not injected) unless a test provides one; restore()
|
|
129
|
-
# only resumes watches persisted with the explicit opt-in — a fresh
|
|
130
|
-
# data_dir never starts a polling thread.
|
|
131
|
-
if folder_watch is None and ingestion_pipeline is not None and data_dir is not None:
|
|
132
|
-
try:
|
|
133
|
-
from latticeai.services.folder_watch import FolderWatchService
|
|
134
|
-
from latticeai.services.obsidian_bridge import ObsidianVaultBridge
|
|
135
|
-
|
|
136
|
-
folder_watch = FolderWatchService(
|
|
137
|
-
pipeline=ingestion_pipeline,
|
|
138
|
-
config_path=Path(data_dir) / "folder_watch.json",
|
|
139
|
-
# A watched vault re-runs the *whole* bridge sync, which is what
|
|
140
|
-
# makes its link edges real; the bridge is handed in rather than
|
|
141
|
-
# imported inside the poller so the watch service keeps knowing
|
|
142
|
-
# nothing about how a vault is parsed.
|
|
143
|
-
vault_bridge=ObsidianVaultBridge(
|
|
144
|
-
pipeline=ingestion_pipeline, knowledge_graph=knowledge_graph,
|
|
145
|
-
),
|
|
146
|
-
)
|
|
147
|
-
folder_watch.restore()
|
|
148
|
-
except Exception: # noqa: BLE001 — watch mode is optional, never blocks routes
|
|
149
|
-
folder_watch = None
|
|
150
|
-
|
|
151
|
-
@router.get("/api/local-agent/status")
|
|
152
|
-
async def local_agent_status(request: Request):
|
|
153
|
-
"""Real on-device runtime status — the 'Local Agent' is the Lattice
|
|
154
|
-
server running on this machine. Every field below is *probed*, not
|
|
155
|
-
hardcoded: filesystem access is a real write/read/delete; the graph and
|
|
156
|
-
watcher are reached live; the mode/handshake are derived from those
|
|
157
|
-
probes. A failing subsystem yields ``degraded``/``error`` honestly.
|
|
158
|
-
|
|
159
|
-
Allowed modes: offline · starting · online · degraded · error.
|
|
160
|
-
"""
|
|
161
|
-
require_user(request)
|
|
162
|
-
started = time.perf_counter()
|
|
163
|
-
errors = []
|
|
164
|
-
|
|
165
|
-
# ── filesystem capability: a real write → read → delete probe ─────────
|
|
166
|
-
fs_ok = False
|
|
167
|
-
probe_dir = Path(data_dir) if data_dir else Path(static_dir).parent
|
|
168
|
-
try:
|
|
169
|
-
probe_dir.mkdir(parents=True, exist_ok=True)
|
|
170
|
-
probe = probe_dir / f".local_agent_probe_{uuid.uuid4().hex}"
|
|
171
|
-
token = uuid.uuid4().hex
|
|
172
|
-
probe.write_text(token, encoding="utf-8")
|
|
173
|
-
fs_ok = probe.read_text(encoding="utf-8") == token
|
|
174
|
-
probe.unlink()
|
|
175
|
-
except Exception as exc:
|
|
176
|
-
errors.append(f"filesystem: {exc}")
|
|
177
|
-
|
|
178
|
-
# ── graph subsystem reachability (real call) ──────────────────────────
|
|
179
|
-
graph_reachable = None
|
|
180
|
-
if knowledge_graph is not None:
|
|
181
|
-
try:
|
|
182
|
-
knowledge_graph.stats()
|
|
183
|
-
graph_reachable = True
|
|
184
|
-
except Exception as exc:
|
|
185
|
-
graph_reachable = False
|
|
186
|
-
errors.append(f"graph: {exc}")
|
|
187
|
-
|
|
188
|
-
# ── watcher + connected sources (real) ────────────────────────────────
|
|
189
|
-
watch: Dict[str, Any] = (
|
|
190
|
-
local_kg_watcher.status()
|
|
191
|
-
if local_kg_watcher
|
|
192
|
-
else {"available": False, "active": {}}
|
|
193
|
-
)
|
|
194
|
-
sources: List[Any] = []
|
|
195
|
-
try:
|
|
196
|
-
if knowledge_graph is not None:
|
|
197
|
-
sources = list((knowledge_graph.local_sources() or {}).get("sources") or [])
|
|
198
|
-
except Exception as exc:
|
|
199
|
-
errors.append(f"sources: {exc}")
|
|
200
|
-
watched = len(watch.get("active", {}) or {})
|
|
201
|
-
|
|
202
|
-
# ── derive mode + handshake from the probes (no constants) ────────────
|
|
203
|
-
if not fs_ok:
|
|
204
|
-
mode = "error"
|
|
205
|
-
elif graph_reachable is False:
|
|
206
|
-
mode = "degraded"
|
|
207
|
-
else:
|
|
208
|
-
mode = "online"
|
|
209
|
-
handshake_ok = fs_ok and graph_reachable is not False
|
|
210
|
-
latency_ms = round((time.perf_counter() - started) * 1000, 2)
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
"agent": {
|
|
214
|
-
"id": "lattice-local-runtime",
|
|
215
|
-
"name": "Lattice Local Agent",
|
|
216
|
-
"kind": "on-device-runtime",
|
|
217
|
-
"online": mode == "online",
|
|
218
|
-
"platform": platform.platform(),
|
|
219
|
-
"machine": platform.machine(),
|
|
220
|
-
"python": platform.python_version(),
|
|
221
|
-
},
|
|
222
|
-
"online": mode == "online",
|
|
223
|
-
"mode": mode,
|
|
224
|
-
"version": _LATTICE_VERSION,
|
|
225
|
-
"pid": os.getpid(),
|
|
226
|
-
"handshake": {
|
|
227
|
-
"ok": handshake_ok,
|
|
228
|
-
"transport": "in-process",
|
|
229
|
-
"latency_ms": latency_ms,
|
|
230
|
-
"detail": "Probed the in-process runtime (filesystem + graph); the local Lattice server is the on-device agent — no separate desktop process.",
|
|
231
|
-
},
|
|
232
|
-
"health": {
|
|
233
|
-
"status": mode,
|
|
234
|
-
"filesystem_access": fs_ok,
|
|
235
|
-
"graph_reachable": graph_reachable,
|
|
236
|
-
"watcher_available": bool(watch.get("available")),
|
|
237
|
-
},
|
|
238
|
-
"filesystem_access": fs_ok,
|
|
239
|
-
"watcher_available": bool(watch.get("available")),
|
|
240
|
-
"connected_folders": len(sources),
|
|
241
|
-
"watched_folders": watched,
|
|
242
|
-
"folders": {"connected": len(sources), "watching": watched},
|
|
243
|
-
"watch": watch,
|
|
244
|
-
"sources": sources,
|
|
245
|
-
"last_seen": datetime.now().isoformat(timespec="seconds"),
|
|
246
|
-
"error": "; ".join(errors) if errors else None,
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
@router.post("/local/list")
|
|
250
|
-
async def local_list_endpoint(req: LocalAccessRequest, request: Request):
|
|
251
|
-
current_user = permission_gateway.require_local_user(request)
|
|
252
|
-
if not req.approved:
|
|
253
|
-
return permission_gateway.local_permission_response(req.path, "list", current_user)
|
|
254
|
-
permission_gateway.require_local_approval(
|
|
255
|
-
token=req.approval_token,
|
|
256
|
-
path=req.path,
|
|
257
|
-
action="list",
|
|
258
|
-
user_email=current_user,
|
|
259
|
-
)
|
|
260
|
-
return tool_response(local_list, req.path)
|
|
261
|
-
|
|
262
|
-
@router.get("/local/list")
|
|
263
|
-
async def local_list_get_endpoint(path: str, request: Request):
|
|
264
|
-
current_user = permission_gateway.require_local_user(request)
|
|
265
|
-
return permission_gateway.local_permission_response(path, "list", current_user)
|
|
266
|
-
|
|
267
|
-
@router.post("/local/read")
|
|
268
|
-
async def local_read_endpoint(req: LocalAccessRequest, request: Request):
|
|
269
|
-
current_user = permission_gateway.require_local_user(request)
|
|
270
|
-
if not req.approved:
|
|
271
|
-
return permission_gateway.local_permission_response(req.path, "read", current_user)
|
|
272
|
-
permission_gateway.require_local_approval(
|
|
273
|
-
token=req.approval_token,
|
|
274
|
-
path=req.path,
|
|
275
|
-
action="read",
|
|
276
|
-
user_email=current_user,
|
|
277
|
-
)
|
|
278
|
-
return tool_response(local_read, req.path)
|
|
279
|
-
|
|
280
|
-
@router.get("/local/serve")
|
|
281
|
-
async def local_serve_file(path: str, request: Request, approval_token: Optional[str] = None):
|
|
282
|
-
current_user = permission_gateway.require_local_user(request)
|
|
283
|
-
permission_gateway.require_local_approval(
|
|
284
|
-
token=approval_token,
|
|
285
|
-
path=path,
|
|
286
|
-
action="read",
|
|
287
|
-
user_email=current_user,
|
|
288
|
-
)
|
|
289
|
-
target = Path(path).expanduser().resolve()
|
|
290
|
-
if not target.exists() or not target.is_file():
|
|
291
|
-
raise http_error(404, "common.file_not_found", resolve_language(request))
|
|
292
|
-
return FileResponse(str(target))
|
|
293
|
-
|
|
294
|
-
# ── v9.8.0 ingestion jobs API (frozen paths — consumed by the frontend) ───
|
|
295
|
-
def _require_pipeline():
|
|
296
|
-
if ingestion_pipeline is None or not ingestion_pipeline.available():
|
|
297
|
-
raise http_error(503, "capture.ingestion_disabled", DEFAULT_LANGUAGE)
|
|
298
|
-
|
|
299
|
-
def _ingestion_write_workspace(request: Request, body_workspace: Optional[str], user: str) -> Optional[str]:
|
|
300
|
-
header = request.headers.get("X-Workspace-Id")
|
|
301
|
-
header = header.strip() if header and header.strip() else None
|
|
302
|
-
supplied = [value for value in (body_workspace, header) if value]
|
|
303
|
-
if len(set(supplied)) > 1:
|
|
304
|
-
raise http_error(403, "common.workspace_mismatch", resolve_language(request))
|
|
305
|
-
requested = supplied[0] if supplied else None
|
|
306
|
-
if workspace_service is None:
|
|
307
|
-
return requested
|
|
308
|
-
try:
|
|
309
|
-
return workspace_service.resolve_write_scope(requested, user or None)
|
|
310
|
-
except PermissionError as exc:
|
|
311
|
-
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
|
312
|
-
|
|
313
28
|
@router.get("/api/ingestion/multimodal")
|
|
314
29
|
async def ingestion_multimodal_status(request: Request):
|
|
315
30
|
"""What this install will do with a picture, a recording, or a video.
|
|
@@ -321,321 +36,9 @@ def create_local_files_router(
|
|
|
321
36
|
server's environment.
|
|
322
37
|
"""
|
|
323
38
|
require_user(request)
|
|
324
|
-
_require_pipeline()
|
|
325
39
|
return ingestion_pipeline.multimodal_status()
|
|
326
40
|
|
|
327
|
-
@router.get("/api/ingestion/jobs")
|
|
328
|
-
async def ingestion_jobs(request: Request, limit: int = 20):
|
|
329
|
-
"""Recent background ingestion jobs (newest first)."""
|
|
330
|
-
require_user(request)
|
|
331
|
-
_require_pipeline()
|
|
332
|
-
limit = max(1, min(int(limit or 20), 100))
|
|
333
|
-
return {"jobs": ingestion_pipeline.list_background_jobs(limit=limit)}
|
|
334
|
-
|
|
335
|
-
@router.get("/api/ingestion/jobs/{job_id}")
|
|
336
|
-
async def ingestion_job_detail(job_id: str, request: Request):
|
|
337
|
-
"""One job with its progress counters and (capped) error records."""
|
|
338
|
-
require_user(request)
|
|
339
|
-
_require_pipeline()
|
|
340
|
-
job = ingestion_pipeline.get_background_job(job_id)
|
|
341
|
-
if job is None:
|
|
342
|
-
raise http_error(404, "ingestion.job_not_found", resolve_language(request))
|
|
343
|
-
return job.as_dict()
|
|
344
|
-
|
|
345
|
-
@router.post("/api/ingestion/jobs/{job_id}/resume")
|
|
346
|
-
async def ingestion_job_resume(job_id: str, request: Request, background_tasks: BackgroundTasks):
|
|
347
|
-
"""Resume an interrupted/partial/failed job from its remaining items."""
|
|
348
|
-
user = require_user(request)
|
|
349
|
-
_require_pipeline()
|
|
350
|
-
job = ingestion_pipeline.get_background_job(job_id)
|
|
351
|
-
if job is None:
|
|
352
|
-
raise http_error(404, "ingestion.job_not_found", resolve_language(request))
|
|
353
|
-
if job.status == "running":
|
|
354
|
-
return {"status": "already_running", "job_id": job_id, "job": job.as_dict()}
|
|
355
|
-
remaining = len(job.remaining_indices())
|
|
356
|
-
if remaining == 0 and job.status == "completed":
|
|
357
|
-
return {"status": "nothing_to_resume", "job_id": job_id, "job": job.as_dict()}
|
|
358
|
-
background_tasks.add_task(
|
|
359
|
-
ingestion_pipeline.resume_background_job, job_id, user_email=user or None,
|
|
360
|
-
)
|
|
361
|
-
return {
|
|
362
|
-
"status": "resuming",
|
|
363
|
-
"job_id": job_id,
|
|
364
|
-
"remaining": remaining,
|
|
365
|
-
"job": job.as_dict(),
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
@router.post("/api/ingestion/folder")
|
|
369
|
-
async def ingestion_folder(req: FolderIngestRequest, request: Request, background_tasks: BackgroundTasks):
|
|
370
|
-
"""Ingest a local folder through the unified pipeline.
|
|
371
|
-
|
|
372
|
-
Reads local disk, so it follows the same approval dance as
|
|
373
|
-
``/local/read`` and ``/knowledge-graph/local/index``: without
|
|
374
|
-
``approved`` + ``approval_token`` the response is a
|
|
375
|
-
``permission_required`` payload. ``background=true`` schedules a job
|
|
376
|
-
(summary includes ``job_id``) and executes it after the response.
|
|
377
|
-
"""
|
|
378
|
-
current_user = permission_gateway.require_local_user(request)
|
|
379
|
-
_require_pipeline()
|
|
380
|
-
workspace_id = _ingestion_write_workspace(request, req.workspace_id, current_user)
|
|
381
|
-
path = (req.path or "").strip()
|
|
382
|
-
if not path:
|
|
383
|
-
raise http_error(400, "common.path_required", resolve_language(request))
|
|
384
|
-
if not req.approved:
|
|
385
|
-
return permission_gateway.local_permission_response(path, "read", current_user)
|
|
386
|
-
permission_gateway.require_local_approval(
|
|
387
|
-
token=req.approval_token,
|
|
388
|
-
path=path,
|
|
389
|
-
action="read",
|
|
390
|
-
user_email=current_user,
|
|
391
|
-
)
|
|
392
|
-
summary = ingestion_pipeline.ingest_folder(
|
|
393
|
-
path,
|
|
394
|
-
recursive=req.recursive,
|
|
395
|
-
background=req.background,
|
|
396
|
-
owner=current_user or None,
|
|
397
|
-
workspace_id=workspace_id,
|
|
398
|
-
user_email=current_user or None,
|
|
399
|
-
)
|
|
400
|
-
job_id = summary.get("job_id")
|
|
401
|
-
if req.background and job_id:
|
|
402
|
-
# Execute after the response is sent; progress is visible via
|
|
403
|
-
# GET /api/ingestion/jobs/{job_id}.
|
|
404
|
-
background_tasks.add_task(
|
|
405
|
-
ingestion_pipeline.run_background_job, job_id, user_email=current_user or None,
|
|
406
|
-
)
|
|
407
|
-
return summary
|
|
408
|
-
|
|
409
|
-
# ── Obsidian vault bridge: manual one-shot sync (v11.1.0) ────────────────
|
|
410
|
-
@router.post("/api/ingestion/obsidian")
|
|
411
|
-
async def ingestion_obsidian(req: ObsidianSyncRequest, request: Request):
|
|
412
|
-
"""Ingest an approved external Obsidian vault through the one gate.
|
|
413
|
-
|
|
414
|
-
Every ``.md`` note goes through the same pipeline door as files and
|
|
415
|
-
folders; on top of that, in-vault links become ``REFERENCES`` edges
|
|
416
|
-
between the note nodes and frontmatter tags become ``Topic`` links. A
|
|
417
|
-
link whose target is missing or ambiguous is reported in
|
|
418
|
-
``links.unresolved`` rather than guessed at. Re-running is idempotent.
|
|
419
|
-
|
|
420
|
-
Reads local disk, so it follows the standard approval dance: without
|
|
421
|
-
``approved`` + ``approval_token`` the answer is a ``permission_required``
|
|
422
|
-
payload.
|
|
423
|
-
"""
|
|
424
|
-
current_user = permission_gateway.require_local_user(request)
|
|
425
|
-
_require_pipeline()
|
|
426
|
-
workspace_id = _ingestion_write_workspace(request, req.workspace_id, current_user)
|
|
427
|
-
path = (req.path or "").strip()
|
|
428
|
-
if not path:
|
|
429
|
-
raise http_error(400, "ingestion.vault_path_required", resolve_language(request))
|
|
430
|
-
if not req.approved:
|
|
431
|
-
return permission_gateway.local_permission_response(path, "read", current_user)
|
|
432
|
-
permission_gateway.require_local_approval(
|
|
433
|
-
token=req.approval_token,
|
|
434
|
-
path=path,
|
|
435
|
-
action="read",
|
|
436
|
-
user_email=current_user,
|
|
437
|
-
)
|
|
438
|
-
from latticeai.services.obsidian_bridge import ObsidianVaultBridge
|
|
439
|
-
|
|
440
|
-
bridge = ObsidianVaultBridge(
|
|
441
|
-
pipeline=ingestion_pipeline, knowledge_graph=knowledge_graph,
|
|
442
|
-
)
|
|
443
|
-
# Walking a vault and embedding its notes is blocking work; the server
|
|
444
|
-
# owns one event loop and it may not sit here (10.9.0 ASYNC gate).
|
|
445
|
-
return await asyncio.to_thread(
|
|
446
|
-
bridge.sync,
|
|
447
|
-
path,
|
|
448
|
-
owner=current_user or None,
|
|
449
|
-
workspace_id=workspace_id,
|
|
450
|
-
user_email=current_user or None,
|
|
451
|
-
dry_run=req.dry_run,
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
# ── interop bridges: Notion export / git history / mail + calendar ───────
|
|
455
|
-
@router.get("/api/ingestion/interop")
|
|
456
|
-
async def interop_status(request: Request):
|
|
457
|
-
"""Which interop bridges this machine can actually run, and why not."""
|
|
458
|
-
require_user(request)
|
|
459
|
-
from latticeai.services.interop_bridges import bridge_status
|
|
460
|
-
|
|
461
|
-
return bridge_status()
|
|
462
|
-
|
|
463
|
-
@router.post("/api/ingestion/interop")
|
|
464
|
-
async def interop_ingest(req: InteropIngestRequest, request: Request):
|
|
465
|
-
"""Read an approved local interop source through the one gate.
|
|
466
|
-
|
|
467
|
-
Same approval dance and the same ``IngestionPipeline`` door as the
|
|
468
|
-
vault bridge: no vendor API, no credentials, nothing leaves the
|
|
469
|
-
machine. ``dry_run`` writes nothing and reports what a real run would.
|
|
470
|
-
"""
|
|
471
|
-
current_user = permission_gateway.require_local_user(request)
|
|
472
|
-
_require_pipeline()
|
|
473
|
-
workspace_id = _ingestion_write_workspace(request, req.workspace_id, current_user)
|
|
474
|
-
path = (req.path or "").strip()
|
|
475
|
-
if not path:
|
|
476
|
-
raise http_error(400, "ingestion.interop_path_required", resolve_language(request))
|
|
477
|
-
from latticeai.services.interop_bridges import build_bridge
|
|
478
|
-
|
|
479
|
-
try:
|
|
480
|
-
bridge = build_bridge(
|
|
481
|
-
req.source, pipeline=ingestion_pipeline, knowledge_graph=knowledge_graph,
|
|
482
|
-
)
|
|
483
|
-
except ValueError:
|
|
484
|
-
raise http_error(
|
|
485
|
-
400,
|
|
486
|
-
"ingestion.interop_unknown_source",
|
|
487
|
-
resolve_language(request),
|
|
488
|
-
source=req.source,
|
|
489
|
-
)
|
|
490
|
-
if not req.approved:
|
|
491
|
-
return permission_gateway.local_permission_response(path, "read", current_user)
|
|
492
|
-
permission_gateway.require_local_approval(
|
|
493
|
-
token=req.approval_token,
|
|
494
|
-
path=path,
|
|
495
|
-
action="read",
|
|
496
|
-
user_email=current_user,
|
|
497
|
-
)
|
|
498
|
-
options: Dict[str, Any] = {}
|
|
499
|
-
if req.max_commits is not None:
|
|
500
|
-
options["max_commits"] = req.max_commits
|
|
501
|
-
# Walking an export and embedding its pages is blocking work; the server
|
|
502
|
-
# owns one event loop and it may not sit here (10.9.0 ASYNC gate).
|
|
503
|
-
return await asyncio.to_thread(
|
|
504
|
-
lambda: bridge.sync(
|
|
505
|
-
path,
|
|
506
|
-
owner=current_user or None,
|
|
507
|
-
workspace_id=workspace_id,
|
|
508
|
-
user_email=current_user or None,
|
|
509
|
-
dry_run=req.dry_run,
|
|
510
|
-
**options,
|
|
511
|
-
)
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
# ── folder watch mode: opt-in, off by default (backlog #8) ────────────────
|
|
515
|
-
def _require_folder_watch(request: Request):
|
|
516
|
-
_require_pipeline()
|
|
517
|
-
if folder_watch is None:
|
|
518
|
-
raise http_error(503, "ingestion.watch_unavailable", resolve_language(request))
|
|
519
|
-
|
|
520
|
-
@router.get("/api/ingestion/watch")
|
|
521
|
-
async def folder_watch_status(request: Request):
|
|
522
|
-
"""Watch-mode status: stored opt-ins, poller state, last scan results."""
|
|
523
|
-
require_user(request)
|
|
524
|
-
_require_folder_watch(request)
|
|
525
|
-
return folder_watch.status()
|
|
526
|
-
|
|
527
|
-
@router.post("/api/ingestion/watch")
|
|
528
|
-
async def folder_watch_enable(req: FolderWatchEnableRequest, request: Request):
|
|
529
|
-
"""Explicitly opt a previously-ingested folder into watch mode.
|
|
530
|
-
|
|
531
|
-
Follows the same local-read approval dance as ``/api/ingestion/folder``.
|
|
532
|
-
Enabling snapshots the folder as the baseline; only *future* new or
|
|
533
|
-
changed files are re-ingested (through the normal pipeline, with the
|
|
534
|
-
watch owner's workspace scope).
|
|
535
|
-
"""
|
|
536
|
-
current_user = permission_gateway.require_local_user(request)
|
|
537
|
-
_require_folder_watch(request)
|
|
538
|
-
workspace_id = _ingestion_write_workspace(request, req.workspace_id, current_user)
|
|
539
|
-
path = (req.path or "").strip()
|
|
540
|
-
if not path:
|
|
541
|
-
raise http_error(400, "common.path_required", resolve_language(request))
|
|
542
|
-
if not req.approved:
|
|
543
|
-
return permission_gateway.local_permission_response(path, "read", current_user)
|
|
544
|
-
permission_gateway.require_local_approval(
|
|
545
|
-
token=req.approval_token,
|
|
546
|
-
path=path,
|
|
547
|
-
action="read",
|
|
548
|
-
user_email=current_user,
|
|
549
|
-
)
|
|
550
|
-
result = folder_watch.enable(
|
|
551
|
-
path,
|
|
552
|
-
owner=current_user or None,
|
|
553
|
-
workspace_id=workspace_id,
|
|
554
|
-
recursive=req.recursive,
|
|
555
|
-
kind=req.kind,
|
|
556
|
-
)
|
|
557
|
-
if result.get("status") == "disabled":
|
|
558
|
-
# Turned off rather than broken: 403 with the reason, the same shape
|
|
559
|
-
# every other opt-in gate in this product answers with.
|
|
560
|
-
raise HTTPException(
|
|
561
|
-
status_code=403,
|
|
562
|
-
detail=result.get("detail")
|
|
563
|
-
or translate("ingestion.vault_watch_disabled", resolve_language(request)),
|
|
564
|
-
)
|
|
565
|
-
if result.get("status") != "ok":
|
|
566
|
-
raise HTTPException(
|
|
567
|
-
status_code=400,
|
|
568
|
-
detail=result.get("detail")
|
|
569
|
-
or translate("ingestion.watch_enable_failed", resolve_language(request)),
|
|
570
|
-
)
|
|
571
|
-
return result
|
|
572
|
-
|
|
573
|
-
@router.delete("/api/ingestion/watch")
|
|
574
|
-
async def folder_watch_disable(
|
|
575
|
-
request: Request,
|
|
576
|
-
watch_id: Optional[str] = None,
|
|
577
|
-
path: Optional[str] = None,
|
|
578
|
-
):
|
|
579
|
-
"""Opt back out of watch mode (removes the stored consent record)."""
|
|
580
|
-
require_user(request)
|
|
581
|
-
_require_folder_watch(request)
|
|
582
|
-
if not watch_id and not path:
|
|
583
|
-
raise http_error(400, "ingestion.watch_selector_required", resolve_language(request))
|
|
584
|
-
result = folder_watch.disable(watch_id=watch_id, path=path)
|
|
585
|
-
if result.get("status") == "not_found":
|
|
586
|
-
raise http_error(404, "ingestion.watch_not_found", resolve_language(request))
|
|
587
|
-
return result
|
|
588
|
-
|
|
589
|
-
@router.post("/local/write")
|
|
590
|
-
async def local_write_endpoint(req: LocalWriteRequest, request: Request):
|
|
591
|
-
current_user = permission_gateway.require_local_user(request)
|
|
592
|
-
if not req.approved:
|
|
593
|
-
return permission_gateway.local_permission_response(req.path, "write", current_user, req.content)
|
|
594
|
-
permission_gateway.require_local_approval(
|
|
595
|
-
token=req.approval_token,
|
|
596
|
-
path=req.path,
|
|
597
|
-
action="write",
|
|
598
|
-
user_email=current_user,
|
|
599
|
-
content=req.content,
|
|
600
|
-
)
|
|
601
|
-
return tool_response(local_write, req.path, req.content)
|
|
602
|
-
|
|
603
|
-
router.include_router(
|
|
604
|
-
create_knowledge_graph_router(
|
|
605
|
-
get_graph=lambda: knowledge_graph,
|
|
606
|
-
require_graph=require_graph,
|
|
607
|
-
require_user=require_user,
|
|
608
|
-
require_admin=require_admin,
|
|
609
|
-
static_dir=static_dir,
|
|
610
|
-
allowed_workspaces_for=allowed_workspaces_for,
|
|
611
|
-
ingestion_pipeline=ingestion_pipeline,
|
|
612
|
-
workspace_service=workspace_service,
|
|
613
|
-
)
|
|
614
|
-
)
|
|
615
|
-
|
|
616
|
-
router.include_router(
|
|
617
|
-
create_local_knowledge_router(
|
|
618
|
-
get_graph=lambda: knowledge_graph,
|
|
619
|
-
require_graph=require_graph,
|
|
620
|
-
require_user=require_user,
|
|
621
|
-
require_local_user=permission_gateway.require_local_user,
|
|
622
|
-
local_permission_response=permission_gateway.local_permission_response,
|
|
623
|
-
require_local_approval=permission_gateway.require_local_approval,
|
|
624
|
-
watcher=local_kg_watcher,
|
|
625
|
-
hooks=hooks,
|
|
626
|
-
workspace_service=workspace_service,
|
|
627
|
-
)
|
|
628
|
-
)
|
|
629
|
-
|
|
630
41
|
return router
|
|
631
42
|
|
|
632
43
|
|
|
633
|
-
__all__ = [
|
|
634
|
-
"FolderIngestRequest",
|
|
635
|
-
"FolderWatchEnableRequest",
|
|
636
|
-
"InteropIngestRequest",
|
|
637
|
-
"LocalAccessRequest",
|
|
638
|
-
"LocalWriteRequest",
|
|
639
|
-
"ObsidianSyncRequest",
|
|
640
|
-
"create_local_files_router",
|
|
641
|
-
]
|
|
44
|
+
__all__ = ["create_local_files_router"]
|