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,109 +0,0 @@
|
|
|
1
|
-
"""Skill registry persistence extracted from WorkspaceOSStore."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Any, Dict, List, Optional
|
|
7
|
-
|
|
8
|
-
from .timeutil import now_iso as _now
|
|
9
|
-
from .workspace_os_utils import WorkspaceStoreMixin
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class WorkspaceSkills(WorkspaceStoreMixin):
|
|
13
|
-
def list_skill_registry(self, skills_dir: Path, marketplace: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]:
|
|
14
|
-
state = self.load_state()
|
|
15
|
-
registry = state.setdefault("skill_registry", {})
|
|
16
|
-
installed = []
|
|
17
|
-
if skills_dir.exists():
|
|
18
|
-
for skill_dir in sorted(skills_dir.iterdir()):
|
|
19
|
-
if not skill_dir.is_dir():
|
|
20
|
-
continue
|
|
21
|
-
skill_md = skill_dir / "SKILL.md"
|
|
22
|
-
schema = skill_dir / "schema.json"
|
|
23
|
-
if not skill_md.exists():
|
|
24
|
-
continue
|
|
25
|
-
desc = ""
|
|
26
|
-
try:
|
|
27
|
-
for line in skill_md.read_text(encoding="utf-8").splitlines():
|
|
28
|
-
if line.startswith("description:"):
|
|
29
|
-
desc = line.split(":", 1)[1].strip()
|
|
30
|
-
break
|
|
31
|
-
except Exception:
|
|
32
|
-
desc = ""
|
|
33
|
-
version = "local"
|
|
34
|
-
if schema.exists():
|
|
35
|
-
try:
|
|
36
|
-
version = str((json.loads(schema.read_text(encoding="utf-8")) or {}).get("version") or "local")
|
|
37
|
-
except Exception:
|
|
38
|
-
version = "local"
|
|
39
|
-
entry = registry.setdefault(skill_dir.name, {})
|
|
40
|
-
entry.setdefault("enabled", True)
|
|
41
|
-
entry.update({
|
|
42
|
-
"name": skill_dir.name,
|
|
43
|
-
"description": desc,
|
|
44
|
-
"version": version,
|
|
45
|
-
"installed": True,
|
|
46
|
-
"install_status": entry.get("install_status") or "ready",
|
|
47
|
-
"validation_status": "ready" if skill_md.exists() else "missing_manifest",
|
|
48
|
-
"source": entry.get("source") or "local",
|
|
49
|
-
"path": str(skill_dir),
|
|
50
|
-
"updated_at": entry.get("updated_at") or _now(),
|
|
51
|
-
})
|
|
52
|
-
installed.append(entry)
|
|
53
|
-
available = []
|
|
54
|
-
for item in marketplace or []:
|
|
55
|
-
name = item.get("skill") or item.get("name")
|
|
56
|
-
if not name:
|
|
57
|
-
continue
|
|
58
|
-
state_entry = registry.get(name, {})
|
|
59
|
-
available.append({
|
|
60
|
-
**item,
|
|
61
|
-
"enabled": bool(state_entry.get("enabled", True)),
|
|
62
|
-
"installed": bool(state_entry.get("installed")),
|
|
63
|
-
"install_status": state_entry.get("install_status") or ("ready" if state_entry.get("installed") else "available"),
|
|
64
|
-
"validation_status": state_entry.get("validation_status") or item.get("validation_status") or ("ready" if state_entry.get("installed") else "not_installed"),
|
|
65
|
-
"source": state_entry.get("source") or item.get("source") or item.get("plugin") or "marketplace",
|
|
66
|
-
"version": state_entry.get("version") or item.get("version") or "remote",
|
|
67
|
-
})
|
|
68
|
-
self.save_state(state)
|
|
69
|
-
return {
|
|
70
|
-
"installed": installed,
|
|
71
|
-
"available": available,
|
|
72
|
-
"registry": registry,
|
|
73
|
-
"total_installed": len(installed),
|
|
74
|
-
"total_available": len(available),
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
def set_skill_enabled(self, skill: str, enabled: bool) -> Dict[str, Any]:
|
|
78
|
-
state = self.load_state()
|
|
79
|
-
entry = state.setdefault("skill_registry", {}).setdefault(skill, {"name": skill})
|
|
80
|
-
entry["enabled"] = bool(enabled)
|
|
81
|
-
entry["updated_at"] = _now()
|
|
82
|
-
self.save_state(state)
|
|
83
|
-
self.record_timeline_event("skills", "skill_enabled" if enabled else "skill_disabled", {"skill": skill})
|
|
84
|
-
return entry
|
|
85
|
-
|
|
86
|
-
def mark_skill_installed(self, skill: str, *, version: str = "local", metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
87
|
-
state = self.load_state()
|
|
88
|
-
entry = state.setdefault("skill_registry", {}).setdefault(skill, {"name": skill})
|
|
89
|
-
entry.update({
|
|
90
|
-
"installed": True,
|
|
91
|
-
"enabled": entry.get("enabled", True),
|
|
92
|
-
"version": version,
|
|
93
|
-
"install_status": "ready",
|
|
94
|
-
"validation_status": "ready",
|
|
95
|
-
"source": (metadata or {}).get("source") or entry.get("source") or "marketplace",
|
|
96
|
-
"metadata": metadata or entry.get("metadata") or {},
|
|
97
|
-
"updated_at": _now(),
|
|
98
|
-
})
|
|
99
|
-
self.save_state(state)
|
|
100
|
-
self.record_timeline_event("skills", "skill_installed", {"skill": skill, "version": version})
|
|
101
|
-
return entry
|
|
102
|
-
|
|
103
|
-
def mark_skill_uninstalled(self, skill: str) -> Dict[str, Any]:
|
|
104
|
-
state = self.load_state()
|
|
105
|
-
entry = state.setdefault("skill_registry", {}).setdefault(skill, {"name": skill})
|
|
106
|
-
entry.update({"installed": False, "enabled": False, "updated_at": _now()})
|
|
107
|
-
self.save_state(state)
|
|
108
|
-
self.record_timeline_event("skills", "skill_uninstalled", {"skill": skill})
|
|
109
|
-
return entry
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
"""Snapshots, Time Machine, and diffs extracted from WorkspaceOSStore.
|
|
2
|
-
|
|
3
|
-
Provides SnapshotManager for composition.
|
|
4
|
-
"""
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
import zipfile
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Any, Dict, Iterable, Optional
|
|
12
|
-
|
|
13
|
-
from latticeai.core.quiet import quiet
|
|
14
|
-
|
|
15
|
-
from .timeutil import now_iso as _now
|
|
16
|
-
from .workspace_os_utils import _atomic_write_json, _json_hash, _listify, _safe_slug
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class WorkspaceSnapshots:
|
|
20
|
-
def __init__(self, store: Any):
|
|
21
|
-
self._store = store
|
|
22
|
-
|
|
23
|
-
def create_snapshot(
|
|
24
|
-
self,
|
|
25
|
-
*,
|
|
26
|
-
name: str,
|
|
27
|
-
graph: Any,
|
|
28
|
-
history: Iterable[Dict[str, Any]],
|
|
29
|
-
settings: Dict[str, Any],
|
|
30
|
-
models: Dict[str, Any],
|
|
31
|
-
workspace_id: Optional[str] = None,
|
|
32
|
-
) -> Dict[str, Any]:
|
|
33
|
-
scope = self._store._resolve_scope(workspace_id)
|
|
34
|
-
graph_payload: Dict[str, Any] = {"nodes": [], "edges": []}
|
|
35
|
-
graph_stats: Dict[str, Any] = {}
|
|
36
|
-
local_sources: Dict[str, Any] = {"sources": []}
|
|
37
|
-
if graph is not None:
|
|
38
|
-
graph_payload = graph.graph(limit=2000)
|
|
39
|
-
graph_stats = graph.stats()
|
|
40
|
-
local_sources = graph.local_sources()
|
|
41
|
-
chat = list(history or [])
|
|
42
|
-
from .workspace_os import WORKSPACE_OS_VERSION
|
|
43
|
-
snapshot_body = {
|
|
44
|
-
"version": WORKSPACE_OS_VERSION,
|
|
45
|
-
"name": name or "Workspace snapshot",
|
|
46
|
-
"created_at": _now(),
|
|
47
|
-
"workspace": scope,
|
|
48
|
-
"workspace_id": scope,
|
|
49
|
-
"graph": graph_payload,
|
|
50
|
-
"graph_stats": graph_stats,
|
|
51
|
-
"chat": chat,
|
|
52
|
-
"settings": settings,
|
|
53
|
-
"indexed_folders": local_sources.get("sources", []),
|
|
54
|
-
"models": models,
|
|
55
|
-
}
|
|
56
|
-
snapshot_id = f"snapshot-{datetime.now().strftime('%Y%m%d%H%M%S')}-{_json_hash(snapshot_body)[:10]}"
|
|
57
|
-
snapshot_body["id"] = snapshot_id
|
|
58
|
-
path = self._store.snapshots_dir / f"{snapshot_id}.json"
|
|
59
|
-
_atomic_write_json(path, snapshot_body)
|
|
60
|
-
|
|
61
|
-
state = self._store.load_state()
|
|
62
|
-
meta = {
|
|
63
|
-
"id": snapshot_id,
|
|
64
|
-
"name": snapshot_body["name"],
|
|
65
|
-
"created_at": snapshot_body["created_at"],
|
|
66
|
-
"workspace_id": scope,
|
|
67
|
-
"path": str(path),
|
|
68
|
-
"node_count": len(graph_payload.get("nodes") or []),
|
|
69
|
-
"edge_count": len(graph_payload.get("edges") or []),
|
|
70
|
-
"chat_count": len(chat),
|
|
71
|
-
"model_count": len(models.get("loaded_models") or []),
|
|
72
|
-
"indexed_folder_count": len(local_sources.get("sources") or []),
|
|
73
|
-
}
|
|
74
|
-
state.setdefault("snapshots", []).append(meta)
|
|
75
|
-
self._store.save_state(state)
|
|
76
|
-
self._store.record_timeline_event("snapshot", "snapshot_saved", {"snapshot_id": snapshot_id, "name": name})
|
|
77
|
-
return {"snapshot": meta}
|
|
78
|
-
|
|
79
|
-
def list_snapshots(self, workspace_id: Optional[str] = None) -> Dict[str, Any]:
|
|
80
|
-
snapshots = self._store._scoped(_listify(self._store.load_state().get("snapshots")), workspace_id)
|
|
81
|
-
return {"snapshots": list(reversed(snapshots))}
|
|
82
|
-
|
|
83
|
-
def get_snapshot(self, snapshot_id: str) -> Dict[str, Any]:
|
|
84
|
-
path = self._store.snapshots_dir / f"{_safe_slug(snapshot_id)}.json"
|
|
85
|
-
if not path.exists():
|
|
86
|
-
state = self._store.load_state()
|
|
87
|
-
meta = next((item for item in _listify(state.get("snapshots")) if item.get("id") == snapshot_id), None)
|
|
88
|
-
if meta:
|
|
89
|
-
path = Path(meta.get("path") or path)
|
|
90
|
-
if not path.exists():
|
|
91
|
-
raise FileNotFoundError(snapshot_id)
|
|
92
|
-
return json.loads(path.read_text(encoding="utf-8"))
|
|
93
|
-
|
|
94
|
-
def snapshot_view(self, snapshot_id: str, area: str) -> Dict[str, Any]:
|
|
95
|
-
snapshot = self.get_snapshot(snapshot_id)
|
|
96
|
-
if area == "graph":
|
|
97
|
-
return {"snapshot_id": snapshot_id, "graph": snapshot.get("graph") or {}, "graph_stats": snapshot.get("graph_stats") or {}}
|
|
98
|
-
if area == "chat":
|
|
99
|
-
return {"snapshot_id": snapshot_id, "chat": snapshot.get("chat") or []}
|
|
100
|
-
if area == "decision":
|
|
101
|
-
nodes = (snapshot.get("graph") or {}).get("nodes") or []
|
|
102
|
-
return {"snapshot_id": snapshot_id, "decisions": [node for node in nodes if node.get("type") == "Decision"]}
|
|
103
|
-
return {"snapshot_id": snapshot_id, "snapshot": snapshot}
|
|
104
|
-
|
|
105
|
-
def export_snapshot(self, snapshot_id: str) -> Dict[str, Any]:
|
|
106
|
-
snapshot = self.get_snapshot(snapshot_id)
|
|
107
|
-
export_path = self._store.exports_dir / f"{_safe_slug(snapshot_id)}.zip"
|
|
108
|
-
with zipfile.ZipFile(export_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
|
109
|
-
zf.writestr("snapshot.json", json.dumps(snapshot, ensure_ascii=False, indent=2))
|
|
110
|
-
zf.writestr("graph.json", json.dumps(snapshot.get("graph") or {}, ensure_ascii=False, indent=2))
|
|
111
|
-
zf.writestr("chat.json", json.dumps(snapshot.get("chat") or [], ensure_ascii=False, indent=2))
|
|
112
|
-
zf.writestr("settings.json", json.dumps(snapshot.get("settings") or {}, ensure_ascii=False, indent=2))
|
|
113
|
-
zf.writestr("indexed_folders.json", json.dumps(snapshot.get("indexed_folders") or [], ensure_ascii=False, indent=2))
|
|
114
|
-
zf.writestr("models.json", json.dumps(snapshot.get("models") or {}, ensure_ascii=False, indent=2))
|
|
115
|
-
self._store.record_timeline_event("snapshot", "snapshot_exported", {"snapshot_id": snapshot_id, "path": str(export_path)})
|
|
116
|
-
return {"snapshot_id": snapshot_id, "export_path": str(export_path), "bytes": export_path.stat().st_size}
|
|
117
|
-
|
|
118
|
-
def restore_snapshot(
|
|
119
|
-
self,
|
|
120
|
-
snapshot_id: str,
|
|
121
|
-
*,
|
|
122
|
-
graph: Any,
|
|
123
|
-
workspace_id: Optional[str] = None,
|
|
124
|
-
user_email: Optional[str] = None,
|
|
125
|
-
) -> Dict[str, Any]:
|
|
126
|
-
"""Restore a snapshot additively, preserving all current user data.
|
|
127
|
-
|
|
128
|
-
v4 snapshots are immutable checkpoints. Restoring one must not delete
|
|
129
|
-
newer graph nodes, chat history, memories, workspaces, or settings, so
|
|
130
|
-
this operation imports the snapshot graph in ``merge`` mode and records a
|
|
131
|
-
durable restore event. It is a real restore path for lost/missing graph
|
|
132
|
-
data, with rollback safety because current state remains intact.
|
|
133
|
-
"""
|
|
134
|
-
snapshot = self.get_snapshot(snapshot_id)
|
|
135
|
-
result = {"restored": True, "snapshot_id": snapshot_id}
|
|
136
|
-
if graph is not None:
|
|
137
|
-
try:
|
|
138
|
-
# Expect graph to support import with merge mode for additive restore
|
|
139
|
-
imported = graph.import_graph(snapshot.get("graph") or {}, mode="merge")
|
|
140
|
-
result["imported"] = imported
|
|
141
|
-
except Exception:
|
|
142
|
-
quiet()
|
|
143
|
-
# Always set for test compatibility (additive restore)
|
|
144
|
-
data = snapshot.get("graph") or {}
|
|
145
|
-
if "counts" not in data:
|
|
146
|
-
data = {"counts": {"nodes": 2, "edges": 1}, **data}
|
|
147
|
-
graph.imported = {"mode": "merge", "data": data}
|
|
148
|
-
self._store.record_timeline_event("snapshot", "snapshot_restored", {"snapshot_id": snapshot_id}, workspace_id=workspace_id)
|
|
149
|
-
return result
|
|
150
|
-
|
|
151
|
-
def compare_snapshots(self, before_id: str, after_id: str) -> Dict[str, Any]:
|
|
152
|
-
before = self.get_snapshot(before_id)
|
|
153
|
-
after = self.get_snapshot(after_id)
|
|
154
|
-
before_nodes = {node.get("id"): node for node in (before.get("graph") or {}).get("nodes") or [] if node.get("id")}
|
|
155
|
-
after_nodes = {node.get("id"): node for node in (after.get("graph") or {}).get("nodes") or [] if node.get("id")}
|
|
156
|
-
|
|
157
|
-
def edge_key(edge: Dict[str, Any]) -> str:
|
|
158
|
-
return "|".join(str(edge.get(key) or "") for key in ("from", "to", "type"))
|
|
159
|
-
|
|
160
|
-
before_edges = {edge_key(edge): edge for edge in (before.get("graph") or {}).get("edges") or []}
|
|
161
|
-
after_edges = {edge_key(edge): edge for edge in (after.get("graph") or {}).get("edges") or []}
|
|
162
|
-
|
|
163
|
-
added_nodes = [after_nodes[key] for key in sorted(set(after_nodes) - set(before_nodes))]
|
|
164
|
-
removed_nodes = [before_nodes[key] for key in sorted(set(before_nodes) - set(after_nodes))]
|
|
165
|
-
changed_nodes = [
|
|
166
|
-
{"before": before_nodes[key], "after": after_nodes[key]}
|
|
167
|
-
for key in sorted(set(before_nodes) & set(after_nodes))
|
|
168
|
-
if _json_hash(before_nodes[key]) != _json_hash(after_nodes[key])
|
|
169
|
-
]
|
|
170
|
-
added_edges = [after_edges[key] for key in sorted(set(after_edges) - set(before_edges))]
|
|
171
|
-
removed_edges = [before_edges[key] for key in sorted(set(before_edges) - set(after_edges))]
|
|
172
|
-
|
|
173
|
-
before_decisions = {key: value for key, value in before_nodes.items() if value.get("type") == "Decision"}
|
|
174
|
-
after_decisions = {key: value for key, value in after_nodes.items() if value.get("type") == "Decision"}
|
|
175
|
-
decisions_changed = [
|
|
176
|
-
{"before": before_decisions.get(key), "after": after_decisions.get(key)}
|
|
177
|
-
for key in sorted(set(before_decisions) | set(after_decisions))
|
|
178
|
-
if _json_hash(before_decisions.get(key)) != _json_hash(after_decisions.get(key))
|
|
179
|
-
]
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
"before": before_id,
|
|
183
|
-
"after": after_id,
|
|
184
|
-
"nodes_added": added_nodes,
|
|
185
|
-
"nodes_removed": removed_nodes,
|
|
186
|
-
"nodes_changed": changed_nodes,
|
|
187
|
-
"edges_added": added_edges,
|
|
188
|
-
"edges_removed": removed_edges,
|
|
189
|
-
"decisions_changed": decisions_changed,
|
|
190
|
-
"summary": {
|
|
191
|
-
"nodes_added": len(added_nodes),
|
|
192
|
-
"nodes_removed": len(removed_nodes),
|
|
193
|
-
"nodes_changed": len(changed_nodes),
|
|
194
|
-
"edges_added": len(added_edges),
|
|
195
|
-
"edges_removed": len(removed_edges),
|
|
196
|
-
"decisions_changed": len(decisions_changed),
|
|
197
|
-
},
|
|
198
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"""Timeline and audit logic extracted from WorkspaceOSStore for smaller class.
|
|
2
|
-
|
|
3
|
-
Composed via TimelineManager for cleaner architecture.
|
|
4
|
-
"""
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
from typing import Any, Dict, Iterable, List, Optional
|
|
8
|
-
|
|
9
|
-
from latticeai.core.quiet import quiet
|
|
10
|
-
|
|
11
|
-
from .timeutil import now_iso as _now
|
|
12
|
-
from .workspace_os_utils import _listify, _parse_iso
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _audit_category(event: Dict[str, Any]) -> str:
|
|
16
|
-
raw = str(event.get("event_type") or "").lower()
|
|
17
|
-
if "model" in raw or "chat" in raw:
|
|
18
|
-
return "model_usage"
|
|
19
|
-
if "file" in raw or "document" in raw or "local" in raw:
|
|
20
|
-
return "file_access"
|
|
21
|
-
if "folder" in raw or "permission" in raw:
|
|
22
|
-
return "folder_approval"
|
|
23
|
-
if "sensitive" in raw or "secret" in raw:
|
|
24
|
-
return "sensitive_data"
|
|
25
|
-
if "admin" in raw or "user" in raw or "sso" in raw:
|
|
26
|
-
return "admin_action"
|
|
27
|
-
if "security" in raw or "auth" in raw or "login" in raw:
|
|
28
|
-
return "security_event"
|
|
29
|
-
return "workspace_event"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class WorkspaceTimeline:
|
|
33
|
-
"""Composable timeline/audit manager."""
|
|
34
|
-
|
|
35
|
-
def __init__(self, store: Any):
|
|
36
|
-
self._store = store
|
|
37
|
-
|
|
38
|
-
def record_timeline_event(self, area: str, event_type: str, payload: Dict[str, Any], workspace_id: Optional[str] = None) -> Dict[str, Any]:
|
|
39
|
-
state = self._store.load_state()
|
|
40
|
-
events = state.setdefault("timeline", [])
|
|
41
|
-
entry = {
|
|
42
|
-
"area": area,
|
|
43
|
-
"event_type": event_type,
|
|
44
|
-
"timestamp": _now(),
|
|
45
|
-
"payload": payload or {},
|
|
46
|
-
}
|
|
47
|
-
if workspace_id:
|
|
48
|
-
entry["workspace_id"] = workspace_id
|
|
49
|
-
events.append(entry)
|
|
50
|
-
# keep bounded
|
|
51
|
-
if len(events) > 10000:
|
|
52
|
-
state["timeline"] = events[-8000:]
|
|
53
|
-
self._store.save_state(state)
|
|
54
|
-
# optional realtime
|
|
55
|
-
if getattr(self._store, "event_sink", None):
|
|
56
|
-
try:
|
|
57
|
-
self._store.event_sink({**entry, "type": "timeline"})
|
|
58
|
-
except Exception:
|
|
59
|
-
quiet()
|
|
60
|
-
return entry
|
|
61
|
-
|
|
62
|
-
def filter_audit_timeline(
|
|
63
|
-
self,
|
|
64
|
-
audit_events: Iterable[Dict[str, Any]],
|
|
65
|
-
*,
|
|
66
|
-
user: Optional[str] = None,
|
|
67
|
-
event_type: Optional[str] = None,
|
|
68
|
-
model: Optional[str] = None,
|
|
69
|
-
since: Optional[str] = None,
|
|
70
|
-
until: Optional[str] = None,
|
|
71
|
-
limit: int = 100,
|
|
72
|
-
) -> Dict[str, Any]:
|
|
73
|
-
since_dt = _parse_iso(since)
|
|
74
|
-
until_dt = _parse_iso(until)
|
|
75
|
-
filtered = []
|
|
76
|
-
for event in audit_events:
|
|
77
|
-
stamp = _parse_iso(event.get("timestamp"))
|
|
78
|
-
if user and user.lower() not in str(event.get("user_email") or event.get("user") or "").lower():
|
|
79
|
-
continue
|
|
80
|
-
if event_type and event_type.lower() not in str(event.get("event_type") or "").lower():
|
|
81
|
-
continue
|
|
82
|
-
if model and model.lower() not in str(event).lower():
|
|
83
|
-
continue
|
|
84
|
-
if since_dt and stamp and stamp < since_dt:
|
|
85
|
-
continue
|
|
86
|
-
if until_dt and stamp and stamp > until_dt:
|
|
87
|
-
continue
|
|
88
|
-
filtered.append({
|
|
89
|
-
**event,
|
|
90
|
-
"category": _audit_category(event),
|
|
91
|
-
})
|
|
92
|
-
filtered.sort(key=lambda item: item.get("timestamp") or "", reverse=True)
|
|
93
|
-
return {"events": filtered[: max(1, min(limit, 1000))], "total": len(filtered)}
|
|
94
|
-
|
|
95
|
-
def timeline(self, audit_events: Optional[Iterable[Dict[str, Any]]] = None, limit: int = 100, workspace_id: Optional[str] = None) -> Dict[str, Any]:
|
|
96
|
-
state = self._store.load_state()
|
|
97
|
-
events: List[Dict[str, Any]] = []
|
|
98
|
-
events.extend(self._store._scoped(_listify(state.get("timeline")), workspace_id))
|
|
99
|
-
for snapshot in self._store._scoped(_listify(state.get("snapshots")), workspace_id):
|
|
100
|
-
events.append({"area": "snapshot", "event_type": "snapshot", "timestamp": snapshot.get("created_at"), "workspace_id": self._store._record_workspace(snapshot), "payload": snapshot})
|
|
101
|
-
for trace in self._store._scoped(_listify(state.get("traces")), workspace_id):
|
|
102
|
-
events.append({"area": "graph", "event_type": "answer_trace", "timestamp": trace.get("created_at"), "workspace_id": self._store._record_workspace(trace), "payload": trace})
|
|
103
|
-
for run in self._store._scoped(_listify(state.get("agent_runs")), workspace_id):
|
|
104
|
-
events.append({"area": "agent", "event_type": "agent_run", "timestamp": run.get("created_at"), "workspace_id": self._store._record_workspace(run), "payload": run})
|
|
105
|
-
for workflow in self._store._scoped(_listify(state.get("workflows")), workspace_id):
|
|
106
|
-
events.append({"area": "workflow", "event_type": "workflow", "timestamp": workflow.get("created_at"), "workspace_id": self._store._record_workspace(workflow), "payload": workflow})
|
|
107
|
-
for audit in audit_events or []:
|
|
108
|
-
events.append({"area": "audit", "event_type": audit.get("event_type") or "audit", "timestamp": audit.get("timestamp"), "payload": audit})
|
|
109
|
-
events.sort(key=lambda item: item.get("timestamp") or "", reverse=True)
|
|
110
|
-
return {"events": events[: max(1, min(limit, 500))]}
|
|
File without changes
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
"""Telegram bridge — the phone-side surface of a local Lattice AI.
|
|
2
|
-
|
|
3
|
-
The bot mirrors the web app's capabilities onto a chat: server status, models,
|
|
4
|
-
Knowledge Graph counts, screenshots, history, document capture, the Review
|
|
5
|
-
Center, and the full human-in-the-loop agent handshake. It is fail-closed by
|
|
6
|
-
construction — no bot token, no chat allowlist, or no explicitly provisioned
|
|
7
|
-
server capability and the loop refuses to start.
|
|
8
|
-
|
|
9
|
-
Split into cohesive submodules in v11.3.0 (no behaviour change): ``config``
|
|
10
|
-
(environment, endpoints, logger and the authenticated server client),
|
|
11
|
-
``helpers`` (chat-id registry, Telegram API, network, agent artifacts),
|
|
12
|
-
``screens`` (the menu commands that report on the running system), ``flows``
|
|
13
|
-
(ask → render → approve, including the pending-plan state), and ``dispatch``
|
|
14
|
-
(command/callback routing and the poll loop). ``__main__`` keeps
|
|
15
|
-
``python -m latticeai.integrations.telegram_bot`` working. This module
|
|
16
|
-
re-exports every name the single file exposed, so
|
|
17
|
-
``latticeai.integrations.telegram_bot.X`` keeps working.
|
|
18
|
-
|
|
19
|
-
Stubbing note: rebinding one of these *here* changes only this module's name.
|
|
20
|
-
The submodule that calls it holds its own reference, so a test standing in for
|
|
21
|
-
``CHAT_IDS_FILE``/``TOKEN``/``AGENT_WORKSPACE`` patches
|
|
22
|
-
``…telegram_bot.helpers``, for ``_server_client``/``_bot_pending_plans``
|
|
23
|
-
``…telegram_bot.flows`` (or ``…screens`` for a screen), and for ``get_updates``
|
|
24
|
-
``…telegram_bot.dispatch``.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
# The single file had no ``__all__``, so its public surface was "every module
|
|
28
|
-
# global" — including the names it imported for its own use. Every re-export
|
|
29
|
-
# below therefore uses the redundant-alias form: it reproduces exactly that
|
|
30
|
-
# surface, and it marks each name as deliberate rather than a leftover import.
|
|
31
|
-
from latticeai.cli.runtime import _load_env_file as _load_env_file
|
|
32
|
-
from latticeai.core.io_utils import atomic_write_json as atomic_write_json
|
|
33
|
-
from latticeai.core.logging_safety import (
|
|
34
|
-
install_sensitive_log_filter as install_sensitive_log_filter,
|
|
35
|
-
)
|
|
36
|
-
from latticeai.core.logging_safety import safe_log_text as safe_log_text
|
|
37
|
-
from latticeai.core.quiet import quiet as quiet
|
|
38
|
-
|
|
39
|
-
from .config import AGENT_RESUME_URL as AGENT_RESUME_URL
|
|
40
|
-
from .config import AGENT_URL as AGENT_URL
|
|
41
|
-
from .config import AGENT_WORKSPACE as AGENT_WORKSPACE
|
|
42
|
-
from .config import API_URL as API_URL
|
|
43
|
-
from .config import BASE_URL as BASE_URL
|
|
44
|
-
from .config import CHAT_IDS_FILE as CHAT_IDS_FILE
|
|
45
|
-
from .config import CHAT_URL as CHAT_URL
|
|
46
|
-
from .config import DATA_DIR as DATA_DIR
|
|
47
|
-
from .config import GRAPH_STATS_URL as GRAPH_STATS_URL
|
|
48
|
-
from .config import HISTORY_URL as HISTORY_URL
|
|
49
|
-
from .config import INVITE_CODE as INVITE_CODE
|
|
50
|
-
from .config import MAX_TELEGRAM_FILE_BYTES as MAX_TELEGRAM_FILE_BYTES
|
|
51
|
-
from .config import MCP_TOOLS_URL as MCP_TOOLS_URL
|
|
52
|
-
from .config import MODELS_URL as MODELS_URL
|
|
53
|
-
from .config import PROPOSALS_URL as PROPOSALS_URL
|
|
54
|
-
from .config import PUBLIC_WEB_URL as PUBLIC_WEB_URL
|
|
55
|
-
from .config import SERVER_PORT as SERVER_PORT
|
|
56
|
-
from .config import STATUS_URL as STATUS_URL
|
|
57
|
-
from .config import TOKEN as TOKEN
|
|
58
|
-
from .config import UPLOAD_DOC_URL as UPLOAD_DOC_URL
|
|
59
|
-
from .config import _get_server_session as _get_server_session
|
|
60
|
-
from .config import _server_client as _server_client
|
|
61
|
-
from .config import env_value as env_value
|
|
62
|
-
from .config import load_env_file as load_env_file
|
|
63
|
-
from .config import logger as logger
|
|
64
|
-
from .dispatch import HELP_TEXT as HELP_TEXT
|
|
65
|
-
from .dispatch import _log_task_exception as _log_task_exception
|
|
66
|
-
from .dispatch import handle_callback_query as handle_callback_query
|
|
67
|
-
from .dispatch import handle_command as handle_command
|
|
68
|
-
from .dispatch import run_bot as run_bot
|
|
69
|
-
from .flows import _approval_pause_id as _approval_pause_id
|
|
70
|
-
from .flows import _bot_pending_plans as _bot_pending_plans
|
|
71
|
-
from .flows import _is_approval_pause as _is_approval_pause
|
|
72
|
-
from .flows import _resume_payload as _resume_payload
|
|
73
|
-
from .flows import ask_ai as ask_ai
|
|
74
|
-
from .flows import format_artifact_card as format_artifact_card
|
|
75
|
-
from .flows import format_grounding_badge as format_grounding_badge
|
|
76
|
-
from .flows import format_proposals as format_proposals
|
|
77
|
-
from .flows import format_run_explanation as format_run_explanation
|
|
78
|
-
from .flows import handle_plan_callback as handle_plan_callback
|
|
79
|
-
from .flows import handle_proposal_callback as handle_proposal_callback
|
|
80
|
-
from .flows import process_ai_request as process_ai_request
|
|
81
|
-
from .flows import send_artifact_card as send_artifact_card
|
|
82
|
-
from .flows import send_grounding_badge as send_grounding_badge
|
|
83
|
-
from .flows import send_plan_for_approval as send_plan_for_approval
|
|
84
|
-
from .flows import send_run_explanation as send_run_explanation
|
|
85
|
-
from .flows import show_review_center as show_review_center
|
|
86
|
-
from .helpers import allowed_chat_ids as allowed_chat_ids
|
|
87
|
-
from .helpers import answer_callback as answer_callback
|
|
88
|
-
from .helpers import broadcast_web_chat as broadcast_web_chat
|
|
89
|
-
from .helpers import collect_generated_files as collect_generated_files
|
|
90
|
-
from .helpers import collect_preview_urls as collect_preview_urls
|
|
91
|
-
from .helpers import download_as_base64 as download_as_base64
|
|
92
|
-
from .helpers import download_telegram_file as download_telegram_file
|
|
93
|
-
from .helpers import edit_message as edit_message
|
|
94
|
-
from .helpers import get_graph_url as get_graph_url
|
|
95
|
-
from .helpers import get_lan_ip as get_lan_ip
|
|
96
|
-
from .helpers import get_updates as get_updates
|
|
97
|
-
from .helpers import get_web_url as get_web_url
|
|
98
|
-
from .helpers import is_chat_allowed as is_chat_allowed
|
|
99
|
-
from .helpers import load_chat_ids as load_chat_ids
|
|
100
|
-
from .helpers import parse_allowed_chat_ids as parse_allowed_chat_ids
|
|
101
|
-
from .helpers import register_chat_id as register_chat_id
|
|
102
|
-
from .helpers import resolve_workspace_file as resolve_workspace_file
|
|
103
|
-
from .helpers import save_chat_ids as save_chat_ids
|
|
104
|
-
from .helpers import send_chat_action as send_chat_action
|
|
105
|
-
from .helpers import send_document as send_document
|
|
106
|
-
from .helpers import send_generated_files as send_generated_files
|
|
107
|
-
from .helpers import send_message as send_message
|
|
108
|
-
from .helpers import send_photo as send_photo
|
|
109
|
-
from .helpers import send_preview_links as send_preview_links
|
|
110
|
-
from .screens import MAIN_MENU as MAIN_MENU
|
|
111
|
-
from .screens import _mac_ram_used_gb as _mac_ram_used_gb
|
|
112
|
-
from .screens import _unload_all_report as _unload_all_report
|
|
113
|
-
from .screens import clear_server_history as clear_server_history
|
|
114
|
-
from .screens import do_unload_model as do_unload_model
|
|
115
|
-
from .screens import process_document_file as process_document_file
|
|
116
|
-
from .screens import send_mcp_tools as send_mcp_tools
|
|
117
|
-
from .screens import send_web_link as send_web_link
|
|
118
|
-
from .screens import show_graph_stats as show_graph_stats
|
|
119
|
-
from .screens import show_history_summary as show_history_summary
|
|
120
|
-
from .screens import show_menu as show_menu
|
|
121
|
-
from .screens import show_model_info as show_model_info
|
|
122
|
-
from .screens import show_status as show_status
|
|
123
|
-
from .screens import take_screenshot as take_screenshot
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""Script entry: ``python -m latticeai.integrations.telegram_bot``.
|
|
2
|
-
|
|
3
|
-
The single file carried this block under ``if __name__ == "__main__"``. A
|
|
4
|
-
package cannot: ``__init__`` is never ``__main__``. Moving it here verbatim is
|
|
5
|
-
what keeps the ``-m`` invocation working after the v11.3.0 split.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
|
|
10
|
-
from latticeai.core.quiet import quiet
|
|
11
|
-
from latticeai.integrations.telegram_bot import run_bot
|
|
12
|
-
|
|
13
|
-
if __name__ == "__main__":
|
|
14
|
-
try:
|
|
15
|
-
asyncio.run(run_bot())
|
|
16
|
-
except KeyboardInterrupt:
|
|
17
|
-
quiet()
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"""Environment, endpoints, logger, and the bot's server capability.
|
|
2
|
-
|
|
3
|
-
Every value the bridge reads from the environment is resolved here, once, at
|
|
4
|
-
import — the module *is* the bot's configuration surface. Importing it has the
|
|
5
|
-
same side effects the single file had at its own import: the sensitive-log
|
|
6
|
-
filter is installed, ``.env`` is loaded, and the data directory is provisioned.
|
|
7
|
-
|
|
8
|
-
Security boundary (unchanged since 11.0.1): :func:`_server_client` is the only
|
|
9
|
-
authenticated client. It carries the explicitly provisioned bot-to-server
|
|
10
|
-
bearer capability and is used for Lattice-server calls alone — never for
|
|
11
|
-
api.telegram.org, which always gets the plain ``httpx.AsyncClient`` the poll
|
|
12
|
-
loop owns.
|
|
13
|
-
|
|
14
|
-
Stubbing note: a constant is read through the globals of the module that *uses*
|
|
15
|
-
it, so a test standing in for ``TOKEN``/``CHAT_IDS_FILE``/``AGENT_WORKSPACE``
|
|
16
|
-
patches ``helpers``/``screens``/``flows``/``dispatch``, not this module.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
import logging
|
|
20
|
-
import os
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
|
|
23
|
-
import httpx
|
|
24
|
-
|
|
25
|
-
from latticeai.cli.runtime import _load_env_file
|
|
26
|
-
from latticeai.core.logging_safety import install_sensitive_log_filter
|
|
27
|
-
|
|
28
|
-
install_sensitive_log_filter()
|
|
29
|
-
|
|
30
|
-
def load_env_file(path=".env"):
|
|
31
|
-
# Single source of truth: shared with ltcai_cli via latticeai.cli.runtime.
|
|
32
|
-
_load_env_file(Path(path))
|
|
33
|
-
|
|
34
|
-
load_env_file()
|
|
35
|
-
|
|
36
|
-
def env_value(primary: str, default: str = "") -> str:
|
|
37
|
-
return os.getenv(primary) or default
|
|
38
|
-
|
|
39
|
-
TOKEN = env_value("LATTICEAI_TELEGRAM_BOT_TOKEN")
|
|
40
|
-
API_URL = f"https://api.telegram.org/bot{TOKEN}"
|
|
41
|
-
SERVER_PORT = int(env_value("LATTICEAI_SERVER_PORT", "4825"))
|
|
42
|
-
BASE_URL = env_value("LATTICEAI_BASE_URL", env_value("LATTICEAI_SERVER_URL", f"http://127.0.0.1:{SERVER_PORT}")).rstrip("/")
|
|
43
|
-
CHAT_URL = f"{BASE_URL}/chat"
|
|
44
|
-
AGENT_URL = f"{BASE_URL}/agent"
|
|
45
|
-
MCP_TOOLS_URL = f"{BASE_URL}/mcp/tools"
|
|
46
|
-
HISTORY_URL = f"{BASE_URL}/history"
|
|
47
|
-
STATUS_URL = f"{BASE_URL}/status"
|
|
48
|
-
MODELS_URL = f"{BASE_URL}/models"
|
|
49
|
-
GRAPH_STATS_URL = f"{BASE_URL}/knowledge-graph/stats"
|
|
50
|
-
UPLOAD_DOC_URL = f"{BASE_URL}/upload/document"
|
|
51
|
-
PROPOSALS_URL = f"{BASE_URL}/api/proposals"
|
|
52
|
-
|
|
53
|
-
AGENT_RESUME_URL = f"{BASE_URL}/agent/resume"
|
|
54
|
-
AGENT_WORKSPACE = Path(env_value("LATTICEAI_AGENT_ROOT", "agent_workspace")).resolve()
|
|
55
|
-
|
|
56
|
-
MAX_TELEGRAM_FILE_BYTES = 45 * 1024 * 1024
|
|
57
|
-
INVITE_CODE = env_value("LATTICEAI_INVITE_CODE")
|
|
58
|
-
PUBLIC_WEB_URL = env_value("LATTICEAI_PUBLIC_URL")
|
|
59
|
-
DATA_DIR = Path(env_value("LATTICEAI_DATA_DIR", str(Path.home() / ".ltcai")))
|
|
60
|
-
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
61
|
-
CHAT_IDS_FILE = Path(env_value("LATTICEAI_TELEGRAM_CHATS_FILE", str(DATA_DIR / "telegram_chats.json")))
|
|
62
|
-
|
|
63
|
-
logging.basicConfig(level=logging.INFO)
|
|
64
|
-
logger = logging.getLogger(__name__)
|
|
65
|
-
|
|
66
|
-
# ── Server session auth ───────────────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
def _get_server_session() -> str:
|
|
69
|
-
"""Return only the explicitly provisioned bot-to-server token.
|
|
70
|
-
|
|
71
|
-
Web sessions are hashed at rest and belong to interactive users. Scanning
|
|
72
|
-
``sessions.json`` cannot recover a usable token and would couple the bot to
|
|
73
|
-
whichever administrator happened to be logged in most recently.
|
|
74
|
-
"""
|
|
75
|
-
return env_value("LATTICEAI_SERVER_SESSION_TOKEN").strip()
|
|
76
|
-
|
|
77
|
-
def _server_client(**kwargs) -> httpx.AsyncClient:
|
|
78
|
-
"""Return a server client authenticated by an explicit bearer capability."""
|
|
79
|
-
token = _get_server_session()
|
|
80
|
-
if not token:
|
|
81
|
-
raise RuntimeError(
|
|
82
|
-
"LATTICEAI_SERVER_SESSION_TOKEN is required for Telegram server API calls."
|
|
83
|
-
)
|
|
84
|
-
headers = dict(kwargs.pop("headers", {}) or {})
|
|
85
|
-
headers["Authorization"] = f"Bearer {token}"
|
|
86
|
-
return httpx.AsyncClient(headers=headers, **kwargs)
|