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,90 +0,0 @@
|
|
|
1
|
-
"""The seam the three ingestion mixins share.
|
|
2
|
-
|
|
3
|
-
``IngestionPipeline`` is assembled from the routing, folder and jobs mixins.
|
|
4
|
-
Each of them reads constructor state it does not own and calls back into
|
|
5
|
-
``ingest`` — the point of the split is that "how one picture is stored" and
|
|
6
|
-
"how a folder is walked" stop sharing a thousand-line file, not that they stop
|
|
7
|
-
sharing ``self``.
|
|
8
|
-
|
|
9
|
-
Typing-only, exactly like :mod:`lattice_brain.graph._kg_contract`: at runtime
|
|
10
|
-
the mixins alias it to ``object``, so the MRO and every method resolution stay
|
|
11
|
-
byte-for-byte what the single-file class had. Adding a cross-mixin call without
|
|
12
|
-
declaring it here is a type error.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
from typing import Any, Dict, List, Optional
|
|
19
|
-
|
|
20
|
-
from ..ingestion_jobs import BackgroundIngestionJob, BackgroundIngestionQueue
|
|
21
|
-
from ..multimodal import MultimodalPorts
|
|
22
|
-
from .models import IngestionItem, IngestionResult
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class IngestionCore:
|
|
26
|
-
"""What any ingestion mixin may assume about ``self``.
|
|
27
|
-
|
|
28
|
-
Never instantiated and never inherited at runtime — see the module
|
|
29
|
-
docstring. Members are declared, not implemented: the implementation lives
|
|
30
|
-
in ``pipeline.py`` or in whichever mixin owns it.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
# ── State owned by IngestionPipeline.__init__ ────────────────────────────
|
|
34
|
-
_kg: Any
|
|
35
|
-
_hooks: Any
|
|
36
|
-
_enable: bool
|
|
37
|
-
_audit: Optional[Any]
|
|
38
|
-
_max_text_bytes: int
|
|
39
|
-
_pipeline_name: str
|
|
40
|
-
_bg_queue: BackgroundIngestionQueue
|
|
41
|
-
_auto_vector_index_opt_in: bool
|
|
42
|
-
_multimodal_opt_in: bool
|
|
43
|
-
_multimodal: MultimodalPorts
|
|
44
|
-
_keyframes: int
|
|
45
|
-
|
|
46
|
-
# ── pipeline.py: the gates, resolved when they are asked ─────────────────
|
|
47
|
-
@property
|
|
48
|
-
def _allow_multimodal(self) -> bool:
|
|
49
|
-
raise NotImplementedError
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
def _allow_video(self) -> bool:
|
|
53
|
-
raise NotImplementedError
|
|
54
|
-
|
|
55
|
-
# ── pipeline.py: the one door every mixin routes back through ────────────
|
|
56
|
-
def available(self) -> bool:
|
|
57
|
-
raise NotImplementedError
|
|
58
|
-
|
|
59
|
-
def ingest(
|
|
60
|
-
self, item: IngestionItem, *, user_email: Optional[str] = None
|
|
61
|
-
) -> IngestionResult:
|
|
62
|
-
raise NotImplementedError
|
|
63
|
-
|
|
64
|
-
# ── routing.py: reached from the folder walk and the modality doors ──────
|
|
65
|
-
def _resolve_file_path(self, item: IngestionItem) -> Path:
|
|
66
|
-
raise NotImplementedError
|
|
67
|
-
|
|
68
|
-
# ── folders.py: the folder-scan allow-list the multimodal gates widen ────
|
|
69
|
-
def _folder_extensions(self) -> frozenset:
|
|
70
|
-
raise NotImplementedError
|
|
71
|
-
|
|
72
|
-
# ── jobs_api.py: scheduling, reached from the folder walk ────────────────
|
|
73
|
-
def schedule_background(
|
|
74
|
-
self,
|
|
75
|
-
items: List[IngestionItem],
|
|
76
|
-
*,
|
|
77
|
-
incremental: bool = True,
|
|
78
|
-
user_email: Optional[str] = None,
|
|
79
|
-
) -> BackgroundIngestionJob:
|
|
80
|
-
raise NotImplementedError
|
|
81
|
-
|
|
82
|
-
def run_background_job(
|
|
83
|
-
self, job_id: str, *, user_email: Optional[str] = None
|
|
84
|
-
) -> Dict[str, Any]:
|
|
85
|
-
raise NotImplementedError
|
|
86
|
-
|
|
87
|
-
def _execute_background_job(
|
|
88
|
-
self, job: BackgroundIngestionJob, *, user_email: Optional[str] = None
|
|
89
|
-
) -> Dict[str, Any]:
|
|
90
|
-
raise NotImplementedError
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"""``.latticeignore`` parsing and matching for the folder walk.
|
|
2
|
-
|
|
3
|
-
A gitignore-like subset: blank lines and ``#`` comments are dropped, patterns
|
|
4
|
-
are ``fnmatch`` globs, and a trailing ``/`` restricts a pattern to directories.
|
|
5
|
-
Patterns match against both the root-relative posix path and the basename, so
|
|
6
|
-
``*.log`` and ``docs/draft.md`` both behave the way a reader expects.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import fnmatch
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Iterable, List
|
|
14
|
-
|
|
15
|
-
from .constants import LATTICEIGNORE_FILENAME
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _load_latticeignore(root: Path) -> List[str]:
|
|
19
|
-
"""Parse ``root/.latticeignore`` → glob patterns (gitignore-like subset)."""
|
|
20
|
-
ignore_file = root / LATTICEIGNORE_FILENAME
|
|
21
|
-
patterns: List[str] = []
|
|
22
|
-
if not ignore_file.is_file():
|
|
23
|
-
return patterns
|
|
24
|
-
try:
|
|
25
|
-
lines = ignore_file.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
26
|
-
except OSError:
|
|
27
|
-
return patterns
|
|
28
|
-
for raw in lines:
|
|
29
|
-
line = raw.strip()
|
|
30
|
-
if not line or line.startswith("#"):
|
|
31
|
-
continue
|
|
32
|
-
patterns.append(line)
|
|
33
|
-
return patterns
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _matches_ignore(
|
|
37
|
-
rel_posix: str, name: str, *, is_dir: bool, patterns: Iterable[str]
|
|
38
|
-
) -> bool:
|
|
39
|
-
"""fnmatch-based .latticeignore matching.
|
|
40
|
-
|
|
41
|
-
- ``pattern/`` matches directories only (files under it never appear
|
|
42
|
-
because ignored directories are pruned during the walk).
|
|
43
|
-
- Patterns match against both the root-relative posix path and the
|
|
44
|
-
basename, so ``*.log`` and ``docs/draft.md`` both behave as expected.
|
|
45
|
-
"""
|
|
46
|
-
for raw in patterns:
|
|
47
|
-
pattern = raw
|
|
48
|
-
if pattern.endswith("/"):
|
|
49
|
-
if not is_dir:
|
|
50
|
-
continue
|
|
51
|
-
pattern = pattern.rstrip("/")
|
|
52
|
-
pattern = pattern.lstrip("/")
|
|
53
|
-
if not pattern:
|
|
54
|
-
continue
|
|
55
|
-
if fnmatch.fnmatch(rel_posix, pattern) or fnmatch.fnmatch(name, pattern):
|
|
56
|
-
return True
|
|
57
|
-
return False
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
"""Walking a folder, and the one-page web hand-off, into the standard door.
|
|
2
|
-
|
|
3
|
-
Neither method is a second ingest path: both build ordinary ``IngestionItem``
|
|
4
|
-
values and hand them to ``IngestionPipeline.ingest``. The folder walk owns the
|
|
5
|
-
filtering order (hard skip-list → hidden → ``.latticeignore`` → extension →
|
|
6
|
-
size) and the choice between ingesting inline and scheduling in the background;
|
|
7
|
-
the web hand-off owns the refusal to fetch or parse anything itself.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import os
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import Any, Dict, Iterable, List, Optional
|
|
15
|
-
|
|
16
|
-
from ._contract import IngestionCore as _Core
|
|
17
|
-
from .constants import (
|
|
18
|
-
DEFAULT_FOLDER_EXTENSIONS,
|
|
19
|
-
DEFAULT_MAX_FILE_BYTES,
|
|
20
|
-
FOLDER_DEFAULT_SKIP_DIRS,
|
|
21
|
-
FOLDER_DOCUMENT_EXTENSIONS,
|
|
22
|
-
FOLDER_MULTIMODAL_EXTENSIONS,
|
|
23
|
-
FOLDER_VIDEO_EXTENSIONS,
|
|
24
|
-
LATTICEIGNORE_FILENAME,
|
|
25
|
-
)
|
|
26
|
-
from .folder_scan import _load_latticeignore, _matches_ignore
|
|
27
|
-
from .models import IngestionItem, IngestionResult
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class IngestionFolderMixin(_Core):
|
|
31
|
-
"""Folder walk + web hand-off. Mixed into ``IngestionPipeline``."""
|
|
32
|
-
|
|
33
|
-
def ingest_web_page(
|
|
34
|
-
self,
|
|
35
|
-
url: str,
|
|
36
|
-
extracted_text: str,
|
|
37
|
-
*,
|
|
38
|
-
title: Optional[str] = None,
|
|
39
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
40
|
-
owner: Optional[str] = None,
|
|
41
|
-
workspace_id: Optional[str] = None,
|
|
42
|
-
captured_at: Optional[str] = None,
|
|
43
|
-
user_email: Optional[str] = None,
|
|
44
|
-
) -> IngestionResult:
|
|
45
|
-
"""Ingest an *already-extracted* web page (see module docstring seam).
|
|
46
|
-
|
|
47
|
-
Fetching/parsing is upstream's responsibility (browser extension /
|
|
48
|
-
tools layer); this wrapper only normalizes ``(url, extracted_text)``
|
|
49
|
-
into an ``IngestionItem(source_type="web_url")`` and routes it through
|
|
50
|
-
the standard :meth:`ingest` door.
|
|
51
|
-
"""
|
|
52
|
-
url = str(url or "").strip()
|
|
53
|
-
if not url:
|
|
54
|
-
return IngestionResult(
|
|
55
|
-
status="failed", source_type="web_url",
|
|
56
|
-
indexing_status="skipped", detail="url required",
|
|
57
|
-
)
|
|
58
|
-
text = str(extracted_text or "")
|
|
59
|
-
if not text.strip():
|
|
60
|
-
return IngestionResult(
|
|
61
|
-
status="failed", source_type="web_url",
|
|
62
|
-
indexing_status="skipped",
|
|
63
|
-
detail=(
|
|
64
|
-
"extracted_text required — the graph layer does not fetch or "
|
|
65
|
-
"parse the web; extraction happens upstream."
|
|
66
|
-
),
|
|
67
|
-
)
|
|
68
|
-
item = IngestionItem(
|
|
69
|
-
source_type="web_url",
|
|
70
|
-
title=title or url,
|
|
71
|
-
text=text,
|
|
72
|
-
source_uri=url,
|
|
73
|
-
owner=owner,
|
|
74
|
-
workspace_id=workspace_id,
|
|
75
|
-
captured_at=captured_at,
|
|
76
|
-
metadata=dict(metadata or {}),
|
|
77
|
-
)
|
|
78
|
-
return self.ingest(item, user_email=user_email or owner)
|
|
79
|
-
|
|
80
|
-
def ingest_folder(
|
|
81
|
-
self,
|
|
82
|
-
root_path: Any,
|
|
83
|
-
*,
|
|
84
|
-
recursive: bool = True,
|
|
85
|
-
background: bool = False,
|
|
86
|
-
extensions: Optional[Iterable[str]] = None,
|
|
87
|
-
max_file_bytes: int = DEFAULT_MAX_FILE_BYTES,
|
|
88
|
-
include_hidden: bool = False,
|
|
89
|
-
max_files: int = 1000,
|
|
90
|
-
max_errors: int = 25,
|
|
91
|
-
owner: Optional[str] = None,
|
|
92
|
-
workspace_id: Optional[str] = None,
|
|
93
|
-
user_email: Optional[str] = None,
|
|
94
|
-
) -> Dict[str, Any]:
|
|
95
|
-
"""Walk ``root_path`` and ingest every eligible file through the pipeline.
|
|
96
|
-
|
|
97
|
-
Filtering, in order: hard skip-list directories (``.git`` …), hidden
|
|
98
|
-
entries (unless ``include_hidden``), root ``.latticeignore`` patterns
|
|
99
|
-
(fnmatch globs; ``dir/`` suffix prunes directories), extension
|
|
100
|
-
allow-list, then ``max_file_bytes``. Text/code files are read inline so
|
|
101
|
-
their content is chunked; ``.pdf`` routes through the file door without
|
|
102
|
-
inline extraction.
|
|
103
|
-
|
|
104
|
-
``background=True`` schedules the built items on the existing
|
|
105
|
-
:class:`BackgroundIngestionQueue` instead of ingesting inline.
|
|
106
|
-
Returns a summary dict with counts and per-file errors (capped at
|
|
107
|
-
``max_errors``).
|
|
108
|
-
"""
|
|
109
|
-
summary: Dict[str, Any] = {
|
|
110
|
-
"root": str(root_path),
|
|
111
|
-
"recursive": bool(recursive),
|
|
112
|
-
"background": bool(background),
|
|
113
|
-
"scanned": 0,
|
|
114
|
-
"matched": 0,
|
|
115
|
-
"ingested": 0,
|
|
116
|
-
"duplicate": 0,
|
|
117
|
-
"failed": 0,
|
|
118
|
-
"skipped": {"ignored": 0, "extension": 0, "too_large": 0, "hidden": 0},
|
|
119
|
-
"truncated": False,
|
|
120
|
-
"errors": [],
|
|
121
|
-
}
|
|
122
|
-
try:
|
|
123
|
-
root = Path(root_path).expanduser()
|
|
124
|
-
except TypeError:
|
|
125
|
-
summary.update(status="failed", detail=f"invalid root path: {root_path!r}")
|
|
126
|
-
return summary
|
|
127
|
-
if not root.is_dir():
|
|
128
|
-
summary.update(status="failed", detail=f"not a directory: {root}")
|
|
129
|
-
return summary
|
|
130
|
-
if not self.available():
|
|
131
|
-
summary.update(
|
|
132
|
-
status="unavailable",
|
|
133
|
-
detail="Knowledge Graph is disabled (LATTICEAI_ENABLE_GRAPH).",
|
|
134
|
-
)
|
|
135
|
-
return summary
|
|
136
|
-
summary["root"] = str(root)
|
|
137
|
-
max_files = max(1, int(max_files))
|
|
138
|
-
max_errors = max(0, int(max_errors))
|
|
139
|
-
max_file_bytes = max(1, int(max_file_bytes))
|
|
140
|
-
allowed_exts = (
|
|
141
|
-
frozenset(str(e).lower() if str(e).startswith(".") else f".{str(e).lower()}" for e in extensions)
|
|
142
|
-
if extensions
|
|
143
|
-
else self._folder_extensions()
|
|
144
|
-
)
|
|
145
|
-
patterns = _load_latticeignore(root)
|
|
146
|
-
errors: List[Dict[str, Any]] = summary["errors"]
|
|
147
|
-
skipped = summary["skipped"]
|
|
148
|
-
items: List[IngestionItem] = []
|
|
149
|
-
|
|
150
|
-
def _record_error(path: Path, detail: str, status: str = "failed") -> None:
|
|
151
|
-
summary["failed"] += 1
|
|
152
|
-
if len(errors) < max_errors:
|
|
153
|
-
errors.append({"path": str(path), "status": status, "detail": detail})
|
|
154
|
-
|
|
155
|
-
for dirpath, dirnames, filenames in os.walk(root):
|
|
156
|
-
current = Path(dirpath)
|
|
157
|
-
rel_dir = current.relative_to(root)
|
|
158
|
-
kept_dirs: List[str] = []
|
|
159
|
-
for name in sorted(dirnames):
|
|
160
|
-
if name in FOLDER_DEFAULT_SKIP_DIRS:
|
|
161
|
-
continue
|
|
162
|
-
if name.startswith(".") and not include_hidden:
|
|
163
|
-
continue
|
|
164
|
-
rel = name if str(rel_dir) == "." else (rel_dir / name).as_posix()
|
|
165
|
-
if _matches_ignore(rel, name, is_dir=True, patterns=patterns):
|
|
166
|
-
skipped["ignored"] += 1
|
|
167
|
-
continue
|
|
168
|
-
kept_dirs.append(name)
|
|
169
|
-
dirnames[:] = kept_dirs if recursive else []
|
|
170
|
-
|
|
171
|
-
for name in sorted(filenames):
|
|
172
|
-
if name == LATTICEIGNORE_FILENAME:
|
|
173
|
-
continue
|
|
174
|
-
summary["scanned"] += 1
|
|
175
|
-
path = current / name
|
|
176
|
-
rel = name if str(rel_dir) == "." else (rel_dir / name).as_posix()
|
|
177
|
-
if name.startswith(".") and not include_hidden:
|
|
178
|
-
skipped["hidden"] += 1
|
|
179
|
-
continue
|
|
180
|
-
if _matches_ignore(rel, name, is_dir=False, patterns=patterns):
|
|
181
|
-
skipped["ignored"] += 1
|
|
182
|
-
continue
|
|
183
|
-
ext = path.suffix.lower()
|
|
184
|
-
if ext not in allowed_exts:
|
|
185
|
-
skipped["extension"] += 1
|
|
186
|
-
continue
|
|
187
|
-
try:
|
|
188
|
-
size = path.stat().st_size
|
|
189
|
-
except OSError as exc:
|
|
190
|
-
_record_error(path, f"stat failed: {exc}")
|
|
191
|
-
continue
|
|
192
|
-
if size > max_file_bytes:
|
|
193
|
-
skipped["too_large"] += 1
|
|
194
|
-
continue
|
|
195
|
-
if len(items) >= max_files:
|
|
196
|
-
summary["truncated"] = True
|
|
197
|
-
break
|
|
198
|
-
item_metadata: Dict[str, Any] = {"relative_path": rel}
|
|
199
|
-
if ext in (FOLDER_MULTIMODAL_EXTENSIONS | FOLDER_VIDEO_EXTENSIONS) and self._allow_multimodal:
|
|
200
|
-
# Routed by modality inside ``ingest``; reading the bytes as
|
|
201
|
-
# UTF-8 here would only produce mojibake.
|
|
202
|
-
source_type = "file"
|
|
203
|
-
elif ext in FOLDER_DOCUMENT_EXTENSIONS:
|
|
204
|
-
source_type = "pdf"
|
|
205
|
-
else:
|
|
206
|
-
source_type = "file"
|
|
207
|
-
try:
|
|
208
|
-
content = path.read_text(encoding="utf-8", errors="ignore")
|
|
209
|
-
except OSError as exc:
|
|
210
|
-
_record_error(path, f"read failed: {exc}")
|
|
211
|
-
continue
|
|
212
|
-
item_metadata["extracted"] = {"content": content, "chars": len(content)}
|
|
213
|
-
items.append(
|
|
214
|
-
IngestionItem(
|
|
215
|
-
source_type=source_type,
|
|
216
|
-
title=name,
|
|
217
|
-
path=str(path),
|
|
218
|
-
source_uri=str(path),
|
|
219
|
-
owner=owner,
|
|
220
|
-
workspace_id=workspace_id,
|
|
221
|
-
metadata=item_metadata,
|
|
222
|
-
)
|
|
223
|
-
)
|
|
224
|
-
if summary["truncated"]:
|
|
225
|
-
break
|
|
226
|
-
|
|
227
|
-
summary["matched"] = len(items)
|
|
228
|
-
if background:
|
|
229
|
-
job = self.schedule_background(
|
|
230
|
-
items, incremental=True, user_email=user_email or owner,
|
|
231
|
-
)
|
|
232
|
-
summary.update(status="scheduled", job_id=job.job_id, scheduled=len(items))
|
|
233
|
-
return summary
|
|
234
|
-
|
|
235
|
-
for item in items:
|
|
236
|
-
result = self.ingest(item, user_email=user_email or owner)
|
|
237
|
-
if result.status == "ok":
|
|
238
|
-
if result.duplicate:
|
|
239
|
-
summary["duplicate"] += 1
|
|
240
|
-
else:
|
|
241
|
-
summary["ingested"] += 1
|
|
242
|
-
else:
|
|
243
|
-
_record_error(Path(item.path or ""), result.detail or result.status, result.status)
|
|
244
|
-
summary["status"] = "ok" if summary["failed"] == 0 else "partial"
|
|
245
|
-
return summary
|
|
246
|
-
|
|
247
|
-
def _folder_extensions(self) -> frozenset:
|
|
248
|
-
"""Folder-scan allow-list — pictures, recordings and films when enabled.
|
|
249
|
-
|
|
250
|
-
Video joins only when this machine can actually decode one, so a scan
|
|
251
|
-
never fills the error list with files it was always going to refuse.
|
|
252
|
-
"""
|
|
253
|
-
if not self._allow_multimodal:
|
|
254
|
-
return DEFAULT_FOLDER_EXTENSIONS
|
|
255
|
-
allowed = DEFAULT_FOLDER_EXTENSIONS | FOLDER_MULTIMODAL_EXTENSIONS
|
|
256
|
-
if self._allow_video:
|
|
257
|
-
return allowed | FOLDER_VIDEO_EXTENSIONS
|
|
258
|
-
return allowed
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
"""Scheduling many items, and running (or resuming) what was scheduled.
|
|
2
|
-
|
|
3
|
-
The pipeline owns "ingest one item"; ``BackgroundIngestionQueue`` owns
|
|
4
|
-
"schedule many and report progress". This mixin is the seam between them:
|
|
5
|
-
per-item errors are recorded and never abort a job, progress is checkpointed
|
|
6
|
-
after every item, and the same method powers both the first run and a resume
|
|
7
|
-
because already-completed items are simply skipped.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
from typing import Any, Dict, List, Optional
|
|
13
|
-
|
|
14
|
-
from ..ingestion_jobs import BackgroundIngestionJob
|
|
15
|
-
from ._contract import IngestionCore as _Core
|
|
16
|
-
from .models import IngestionItem
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class IngestionJobsMixin(_Core):
|
|
20
|
-
"""Background job scheduling. Mixed into ``IngestionPipeline``."""
|
|
21
|
-
|
|
22
|
-
# --- Large candidate #1: background / incremental scheduling (slice) ---
|
|
23
|
-
def schedule_background(
|
|
24
|
-
self,
|
|
25
|
-
items: List[IngestionItem],
|
|
26
|
-
*,
|
|
27
|
-
incremental: bool = True,
|
|
28
|
-
user_email: Optional[str] = None,
|
|
29
|
-
) -> BackgroundIngestionJob:
|
|
30
|
-
"""Schedule items for background incremental indexing.
|
|
31
|
-
|
|
32
|
-
Returns a job handle. Actual execution can be driven by caller
|
|
33
|
-
(or future worker) calling pipeline.ingest on each — or through
|
|
34
|
-
:meth:`run_background_job`. This seam enables large-corpus scale
|
|
35
|
-
without blocking user requests.
|
|
36
|
-
"""
|
|
37
|
-
job = self._bg_queue.schedule(items, incremental=incremental, user_email=user_email)
|
|
38
|
-
# mark initial status on results concept (jobs track)
|
|
39
|
-
return job
|
|
40
|
-
|
|
41
|
-
def get_background_job(self, job_id: str) -> Optional[BackgroundIngestionJob]:
|
|
42
|
-
return self._bg_queue.get(job_id)
|
|
43
|
-
|
|
44
|
-
def list_background_jobs(self, limit: int = 20) -> List[Dict[str, Any]]:
|
|
45
|
-
"""Recent jobs (newest first) in the frozen ``/api/ingestion`` schema."""
|
|
46
|
-
return [job.as_dict() for job in self._bg_queue.list_recent(limit=limit)]
|
|
47
|
-
|
|
48
|
-
def run_background_job(
|
|
49
|
-
self, job_id: str, *, user_email: Optional[str] = None
|
|
50
|
-
) -> Dict[str, Any]:
|
|
51
|
-
"""Execute a queued/interrupted job's remaining items.
|
|
52
|
-
|
|
53
|
-
Per-item errors are recorded (capped) and never abort the job. The
|
|
54
|
-
final status is ``completed`` (all done), ``partial`` (some done),
|
|
55
|
-
or ``failed`` (nothing done). Already-completed items are skipped, so
|
|
56
|
-
the same method safely powers both first-run and resume.
|
|
57
|
-
"""
|
|
58
|
-
job = self._bg_queue.get(job_id)
|
|
59
|
-
if job is None:
|
|
60
|
-
return {"status": "not_found", "job_id": job_id}
|
|
61
|
-
if job.status == "running":
|
|
62
|
-
return job.as_dict()
|
|
63
|
-
return self._execute_background_job(job, user_email=user_email)
|
|
64
|
-
|
|
65
|
-
def resume_background_job(
|
|
66
|
-
self, job_id: str, *, user_email: Optional[str] = None
|
|
67
|
-
) -> Dict[str, Any]:
|
|
68
|
-
"""Resume an interrupted/partial/failed job from its remaining items."""
|
|
69
|
-
return self.run_background_job(job_id, user_email=user_email)
|
|
70
|
-
|
|
71
|
-
def _execute_background_job(
|
|
72
|
-
self, job: BackgroundIngestionJob, *, user_email: Optional[str] = None
|
|
73
|
-
) -> Dict[str, Any]:
|
|
74
|
-
job.status = "running"
|
|
75
|
-
# Retried items get a fresh verdict: reset failure state for this run.
|
|
76
|
-
job.failed = 0
|
|
77
|
-
job.errors = []
|
|
78
|
-
job.touch()
|
|
79
|
-
self._bg_queue.save(job)
|
|
80
|
-
runner_email = user_email or job.user_email
|
|
81
|
-
for index in job.remaining_indices():
|
|
82
|
-
item = job.items[index]
|
|
83
|
-
try:
|
|
84
|
-
result = self.ingest(item, user_email=runner_email or item.owner)
|
|
85
|
-
status, detail = result.status, result.detail
|
|
86
|
-
except Exception as exc: # noqa: BLE001 — per-item isolation: keep going
|
|
87
|
-
status, detail = "failed", str(exc)
|
|
88
|
-
if status == "ok":
|
|
89
|
-
job.done_indices.add(index)
|
|
90
|
-
else:
|
|
91
|
-
job.record_error(index, item, detail or status)
|
|
92
|
-
job.processed = len(job.done_indices)
|
|
93
|
-
job.touch()
|
|
94
|
-
# Checkpoint per item: a crash here must cost at most the item in
|
|
95
|
-
# flight, never the whole job's progress. One small UPDATE against
|
|
96
|
-
# an ingest (parse + chunk + embed) is noise.
|
|
97
|
-
self._bg_queue.save(job)
|
|
98
|
-
job.processed = len(job.done_indices)
|
|
99
|
-
if job.total == 0 or job.processed >= job.total:
|
|
100
|
-
job.status = "completed"
|
|
101
|
-
elif job.processed > 0:
|
|
102
|
-
job.status = "partial"
|
|
103
|
-
else:
|
|
104
|
-
job.status = "failed"
|
|
105
|
-
job.touch()
|
|
106
|
-
self._bg_queue.save(job)
|
|
107
|
-
return job.as_dict()
|