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,614 +0,0 @@
|
|
|
1
|
-
"""Question-driven everyday automation intelligence (v9.4.0).
|
|
2
|
-
|
|
3
|
-
Users repeat themselves: the same "오늘 뭐 해야 하지?", "프로젝트 상태 정리해줘",
|
|
4
|
-
"그 폴더에 새 문서 뭐 들어왔어?" questions come back every day. This service
|
|
5
|
-
watches for those patterns and turns them into one-click, consent-first
|
|
6
|
-
automation suggestions:
|
|
7
|
-
|
|
8
|
-
* **question_patterns** — mines the user's own chat history for recurring
|
|
9
|
-
question intents. Clustering is deterministic and local (token-signature
|
|
10
|
-
similarity, no model call), so the evidence shown to the user is their own
|
|
11
|
-
literal questions.
|
|
12
|
-
* **suggestions** — converts recurring patterns and active local knowledge
|
|
13
|
-
sources (folders the user connected to the Brain) into concrete automation
|
|
14
|
-
proposals: a scheduled answer for a recurring question, a digest for a busy
|
|
15
|
-
knowledge folder, or one of the starter recipes when the intent matches.
|
|
16
|
-
* **build_suggestion_workflow** — turns an accepted suggestion into a
|
|
17
|
-
workflow definition using the same consent-first shape as the starter
|
|
18
|
-
recipes: installed as a disabled draft, review-queue gated, local-only,
|
|
19
|
-
no external actions.
|
|
20
|
-
* **overview** — one payload for the automation surface: suggestions,
|
|
21
|
-
installed automations with their enable state, and pattern evidence.
|
|
22
|
-
|
|
23
|
-
Every suggestion id is deterministic, so install calls are idempotent and the
|
|
24
|
-
UI can safely re-request suggestions without duplicates.
|
|
25
|
-
|
|
26
|
-
v9.8.0 quality layer (additive): every suggestion carries a deterministic
|
|
27
|
-
``confidence`` score plus its ``confidence_factors`` evidence (repeat count,
|
|
28
|
-
distinct phrasings, intent match, related Brain nodes / indexed files);
|
|
29
|
-
suggestions below a minimum confidence are suppressed, duplicate suggestions
|
|
30
|
-
targeting an already-suggested or already-installed starter recipe are
|
|
31
|
-
deduplicated, and the response reports the suppression counters under
|
|
32
|
-
``quality``.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
from __future__ import annotations
|
|
36
|
-
|
|
37
|
-
import hashlib
|
|
38
|
-
import logging
|
|
39
|
-
import re
|
|
40
|
-
from dataclasses import dataclass, field
|
|
41
|
-
from typing import Any, Dict, List, Optional
|
|
42
|
-
|
|
43
|
-
from latticeai.core.timeutil import now_iso as _now
|
|
44
|
-
|
|
45
|
-
LOGGER = logging.getLogger(__name__)
|
|
46
|
-
|
|
47
|
-
_MIN_PATTERN_COUNT = 2
|
|
48
|
-
_MAX_HISTORY = 4000
|
|
49
|
-
_SIGNATURE_SIMILARITY = 0.6
|
|
50
|
-
|
|
51
|
-
# Suggestion quality gates (v9.8.0, additive). Suggestions below the minimum
|
|
52
|
-
# confidence are suppressed (insufficient evidence); suggestions between the
|
|
53
|
-
# minimum and the low-confidence threshold are shown but flagged so the UI
|
|
54
|
-
# can render them less prominently.
|
|
55
|
-
_MIN_SUGGESTION_CONFIDENCE = 0.35
|
|
56
|
-
_LOW_CONFIDENCE_THRESHOLD = 0.5
|
|
57
|
-
_KG_GROUNDING_LIMIT = 5
|
|
58
|
-
#: Charged when the graph *was* consulted and found nothing related. v11.2.0
|
|
59
|
-
#: fixes a dead policy: the floor above was unreachable for recurring questions
|
|
60
|
-
#: (the cheapest possible question — asked twice, one phrasing, no recipe —
|
|
61
|
-
#: scored 0.475), so the suppression branch could only ever be exercised by a
|
|
62
|
-
#: test that moved the constant. Absence of grounding is real evidence, and
|
|
63
|
-
#: charging for it is what makes the floor mean something again.
|
|
64
|
-
_UNGROUNDED_PENALTY = 0.15
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _question_confidence(
|
|
68
|
-
count: int,
|
|
69
|
-
examples: List[str],
|
|
70
|
-
recipe_id: Optional[str],
|
|
71
|
-
kg_related: Optional[int],
|
|
72
|
-
) -> tuple:
|
|
73
|
-
"""Deterministic confidence for a recurring-question suggestion.
|
|
74
|
-
|
|
75
|
-
Evidence factors: how often the question repeats, how many distinct
|
|
76
|
-
phrasings exist, whether it maps onto a known starter-recipe intent, and
|
|
77
|
-
what the Brain knows about it.
|
|
78
|
-
|
|
79
|
-
That last factor is three-valued on purpose. ``None`` means the graph could
|
|
80
|
-
not be consulted — unknown, so nothing is added or taken away. A positive
|
|
81
|
-
count is supporting evidence. **Zero** is not the same as unknown: the
|
|
82
|
-
graph was asked, and it knows nothing about this question, which is a
|
|
83
|
-
reason to be less sure rather than a reason to be neutral.
|
|
84
|
-
"""
|
|
85
|
-
score = 0.3 + 0.5 * min(1.0, (int(count) - 1) / 4)
|
|
86
|
-
score += min(0.15, 0.05 * len(examples or []))
|
|
87
|
-
if recipe_id:
|
|
88
|
-
score += 0.15
|
|
89
|
-
if kg_related:
|
|
90
|
-
score += min(0.2, 0.05 * int(kg_related))
|
|
91
|
-
elif kg_related == 0:
|
|
92
|
-
score -= _UNGROUNDED_PENALTY
|
|
93
|
-
factors = {
|
|
94
|
-
"repeat_count": int(count),
|
|
95
|
-
"distinct_examples": len(examples or []),
|
|
96
|
-
"intent_match": bool(recipe_id),
|
|
97
|
-
"kg_related_nodes": kg_related,
|
|
98
|
-
"kg_grounded": None if kg_related is None else bool(kg_related),
|
|
99
|
-
}
|
|
100
|
-
return round(max(0.0, min(1.0, score)), 2), factors
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def _source_confidence(indexed: int, watch_enabled: bool) -> tuple:
|
|
104
|
-
"""Deterministic confidence for a knowledge-source digest suggestion."""
|
|
105
|
-
score = 0.25 + 0.6 * min(1.0, int(indexed) / 25)
|
|
106
|
-
if watch_enabled:
|
|
107
|
-
score += 0.1
|
|
108
|
-
factors = {"indexed_files": int(indexed), "watch_enabled": bool(watch_enabled)}
|
|
109
|
-
return round(min(1.0, score), 2), factors
|
|
110
|
-
|
|
111
|
-
_QUESTION_HINT_RE = re.compile(
|
|
112
|
-
r"(\?|어때|뭐야|뭐가|뭘까|알려줘|보여줘|정리해|요약해|정리 좀|요약 좀|해줘"
|
|
113
|
-
r"|what|how|why|when|where|status|summar|remind|list|show me|tell me)",
|
|
114
|
-
re.IGNORECASE,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
_STOPWORDS = {
|
|
118
|
-
"the", "a", "an", "is", "are", "was", "to", "of", "in", "on", "for", "and",
|
|
119
|
-
"or", "me", "my", "you", "please", "can", "could", "would", "it", "this",
|
|
120
|
-
"that", "do", "does", "what", "how", "about",
|
|
121
|
-
"좀", "그", "이", "저", "것", "거", "게", "내", "제", "나", "너", "우리",
|
|
122
|
-
"해줘", "해주세요", "주세요", "합니다", "있어", "있나", "있는지", "어떻게",
|
|
123
|
-
"뭐야", "뭐가", "알려줘", "보여줘",
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
# Intent buckets map recurring question language onto the starter recipes.
|
|
127
|
-
_INTENT_RULES = (
|
|
128
|
-
(
|
|
129
|
-
"digest",
|
|
130
|
-
re.compile(r"(오늘|하루|today|daily).{0,12}(정리|요약|digest|summary|기억|메모)|"
|
|
131
|
-
r"(정리|요약).{0,8}(해줘|해 줘|부탁)|summar(y|ize)", re.IGNORECASE),
|
|
132
|
-
"daily-memory-digest",
|
|
133
|
-
),
|
|
134
|
-
(
|
|
135
|
-
"project_review",
|
|
136
|
-
re.compile(r"(프로젝트|project|진행|progress|status|상태|주간|weekly|이번 주)", re.IGNORECASE),
|
|
137
|
-
"weekly-project-review",
|
|
138
|
-
),
|
|
139
|
-
(
|
|
140
|
-
"follow_up",
|
|
141
|
-
re.compile(r"(리마인드|remind|챙겨|잊지|deadline|마감|follow.?up|나중에|까먹)", re.IGNORECASE),
|
|
142
|
-
"follow-up-radar",
|
|
143
|
-
),
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def _tokens(text: str) -> List[str]:
|
|
148
|
-
return [
|
|
149
|
-
token
|
|
150
|
-
for token in re.findall(r"[\w가-힣]+", str(text or "").lower())
|
|
151
|
-
if len(token) > 1 and token not in _STOPWORDS
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def _signature(tokens: List[str]) -> frozenset:
|
|
156
|
-
return frozenset(tokens)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _similarity(left: frozenset, right: frozenset) -> float:
|
|
160
|
-
if not left or not right:
|
|
161
|
-
return 0.0
|
|
162
|
-
return len(left & right) / len(left | right)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def _stable_id(prefix: str, seed: str) -> str:
|
|
166
|
-
return f"{prefix}-{hashlib.sha256(seed.encode('utf-8')).hexdigest()[:10]}"
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
@dataclass
|
|
170
|
-
class _Pattern:
|
|
171
|
-
representative: str
|
|
172
|
-
signature: frozenset
|
|
173
|
-
count: int = 1
|
|
174
|
-
last_asked: str = ""
|
|
175
|
-
intent: str = "recurring_question"
|
|
176
|
-
recipe_id: Optional[str] = None
|
|
177
|
-
examples: List[str] = field(default_factory=list)
|
|
178
|
-
|
|
179
|
-
def as_dict(self) -> Dict[str, Any]:
|
|
180
|
-
return {
|
|
181
|
-
"id": _stable_id("pat", " ".join(sorted(self.signature))),
|
|
182
|
-
"representative": self.representative[:160],
|
|
183
|
-
"count": self.count,
|
|
184
|
-
"last_asked": self.last_asked,
|
|
185
|
-
"intent": self.intent,
|
|
186
|
-
"recipe_id": self.recipe_id,
|
|
187
|
-
"examples": self.examples[:3],
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
class AutomationIntelligenceService:
|
|
192
|
-
def __init__(
|
|
193
|
-
self,
|
|
194
|
-
*,
|
|
195
|
-
conversation_store: Any = None,
|
|
196
|
-
knowledge_graph: Any = None,
|
|
197
|
-
store: Any = None,
|
|
198
|
-
enable_graph: bool = True,
|
|
199
|
-
) -> None:
|
|
200
|
-
self._conversations = conversation_store
|
|
201
|
-
self._kg = knowledge_graph
|
|
202
|
-
self._store = store
|
|
203
|
-
self._enable_graph = bool(enable_graph and knowledge_graph is not None)
|
|
204
|
-
|
|
205
|
-
# ── question pattern mining ──────────────────────────────────────────
|
|
206
|
-
|
|
207
|
-
def _user_questions(
|
|
208
|
-
self, *, user_email: Optional[str], workspace_id: Optional[str]
|
|
209
|
-
) -> List[Dict[str, Any]]:
|
|
210
|
-
if self._conversations is None:
|
|
211
|
-
return []
|
|
212
|
-
try:
|
|
213
|
-
items = self._conversations.history(
|
|
214
|
-
user_email=user_email,
|
|
215
|
-
allowed_workspaces={workspace_id} if workspace_id is not None else None,
|
|
216
|
-
include_legacy_global=workspace_id is None,
|
|
217
|
-
limit=_MAX_HISTORY,
|
|
218
|
-
)
|
|
219
|
-
except Exception:
|
|
220
|
-
LOGGER.exception("automation intelligence history read failed")
|
|
221
|
-
return []
|
|
222
|
-
questions = []
|
|
223
|
-
for item in items:
|
|
224
|
-
if item.get("role") != "user":
|
|
225
|
-
continue
|
|
226
|
-
content = str(item.get("content") or "").strip()
|
|
227
|
-
if not content or len(content) < 6 or content.startswith("/"):
|
|
228
|
-
continue
|
|
229
|
-
if not _QUESTION_HINT_RE.search(content):
|
|
230
|
-
continue
|
|
231
|
-
questions.append({"content": content, "timestamp": str(item.get("timestamp") or "")})
|
|
232
|
-
return questions
|
|
233
|
-
|
|
234
|
-
def question_patterns(
|
|
235
|
-
self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
|
|
236
|
-
) -> Dict[str, Any]:
|
|
237
|
-
questions = self._user_questions(user_email=user_email, workspace_id=workspace_id)
|
|
238
|
-
patterns: List[_Pattern] = []
|
|
239
|
-
for question in questions:
|
|
240
|
-
tokens = _tokens(question["content"])
|
|
241
|
-
if len(tokens) < 2:
|
|
242
|
-
continue
|
|
243
|
-
signature = _signature(tokens)
|
|
244
|
-
match = None
|
|
245
|
-
for pattern in patterns:
|
|
246
|
-
if _similarity(pattern.signature, signature) >= _SIGNATURE_SIMILARITY:
|
|
247
|
-
match = pattern
|
|
248
|
-
break
|
|
249
|
-
if match is None:
|
|
250
|
-
patterns.append(
|
|
251
|
-
_Pattern(
|
|
252
|
-
representative=question["content"],
|
|
253
|
-
signature=signature,
|
|
254
|
-
last_asked=question["timestamp"],
|
|
255
|
-
examples=[question["content"]],
|
|
256
|
-
)
|
|
257
|
-
)
|
|
258
|
-
continue
|
|
259
|
-
match.count += 1
|
|
260
|
-
# Union keeps the cluster stable as phrasing drifts.
|
|
261
|
-
match.signature = match.signature | signature
|
|
262
|
-
if question["timestamp"] >= match.last_asked:
|
|
263
|
-
match.last_asked = question["timestamp"]
|
|
264
|
-
match.representative = question["content"]
|
|
265
|
-
if question["content"] not in match.examples:
|
|
266
|
-
match.examples.append(question["content"])
|
|
267
|
-
|
|
268
|
-
recurring = [p for p in patterns if p.count >= _MIN_PATTERN_COUNT]
|
|
269
|
-
for pattern in recurring:
|
|
270
|
-
for intent, rule, recipe_id in _INTENT_RULES:
|
|
271
|
-
if rule.search(pattern.representative):
|
|
272
|
-
pattern.intent = intent
|
|
273
|
-
pattern.recipe_id = recipe_id
|
|
274
|
-
break
|
|
275
|
-
|
|
276
|
-
recurring.sort(key=lambda p: (p.count, p.last_asked), reverse=True)
|
|
277
|
-
return {
|
|
278
|
-
"questions_scanned": len(questions),
|
|
279
|
-
"patterns": [p.as_dict() for p in recurring[:20]],
|
|
280
|
-
"generated_at": _now(),
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
# ── knowledge sources ────────────────────────────────────────────────
|
|
284
|
-
|
|
285
|
-
def _knowledge_sources(self) -> List[Dict[str, Any]]:
|
|
286
|
-
if not self._enable_graph or not hasattr(self._kg, "local_sources"):
|
|
287
|
-
return []
|
|
288
|
-
try:
|
|
289
|
-
return list(self._kg.local_sources().get("sources") or [])
|
|
290
|
-
except Exception:
|
|
291
|
-
LOGGER.exception("automation intelligence source read failed")
|
|
292
|
-
return []
|
|
293
|
-
|
|
294
|
-
def _kg_related_count(
|
|
295
|
-
self, text: str, *, workspace_id: Optional[str]
|
|
296
|
-
) -> Optional[int]:
|
|
297
|
-
"""Count Brain nodes related to a recurring question (grounding evidence).
|
|
298
|
-
|
|
299
|
-
Returns ``None`` when the graph is unavailable so confidence scoring can
|
|
300
|
-
distinguish "grounding impossible" from "grounded with zero hits".
|
|
301
|
-
Scoping mirrors the conversation-history read: an explicit workspace is
|
|
302
|
-
strict, no workspace falls back to the unscoped/legacy view.
|
|
303
|
-
"""
|
|
304
|
-
if not self._enable_graph or not hasattr(self._kg, "search"):
|
|
305
|
-
return None
|
|
306
|
-
try:
|
|
307
|
-
report = self._kg.search(
|
|
308
|
-
str(text or "")[:200],
|
|
309
|
-
limit=_KG_GROUNDING_LIMIT,
|
|
310
|
-
allowed_workspaces={workspace_id} if workspace_id is not None else None,
|
|
311
|
-
include_legacy_global=workspace_id is None,
|
|
312
|
-
)
|
|
313
|
-
except Exception:
|
|
314
|
-
LOGGER.exception("automation intelligence KG grounding failed")
|
|
315
|
-
return None
|
|
316
|
-
if not isinstance(report, dict):
|
|
317
|
-
return None
|
|
318
|
-
return len(report.get("matches") or [])
|
|
319
|
-
|
|
320
|
-
# ── suggestions ──────────────────────────────────────────────────────
|
|
321
|
-
|
|
322
|
-
def _installed_workflows(
|
|
323
|
-
self, *, workspace_id: Optional[str]
|
|
324
|
-
) -> tuple:
|
|
325
|
-
"""Map installed automation workflows by suggestion id and recipe id.
|
|
326
|
-
|
|
327
|
-
The recipe map lets a recurring-question suggestion that targets an
|
|
328
|
-
already-installed starter recipe surface as *installed* instead of
|
|
329
|
-
re-suggesting the same automation — the install API is idempotent on
|
|
330
|
-
exactly this provenance, so the read side must agree.
|
|
331
|
-
"""
|
|
332
|
-
by_suggestion: Dict[str, Dict[str, Any]] = {}
|
|
333
|
-
by_recipe: Dict[str, Dict[str, Any]] = {}
|
|
334
|
-
if self._store is None:
|
|
335
|
-
return by_suggestion, by_recipe
|
|
336
|
-
try:
|
|
337
|
-
workflows = self._store.list_workflows(workspace_id=workspace_id).get("workflows") or []
|
|
338
|
-
except Exception:
|
|
339
|
-
LOGGER.exception("automation intelligence workflow read failed")
|
|
340
|
-
return by_suggestion, by_recipe
|
|
341
|
-
for workflow in workflows:
|
|
342
|
-
metadata = (workflow or {}).get("metadata") or {}
|
|
343
|
-
created_from = metadata.get("created_from")
|
|
344
|
-
if created_from == "automation_suggestion" and metadata.get("suggestion_id"):
|
|
345
|
-
by_suggestion[str(metadata["suggestion_id"])] = workflow
|
|
346
|
-
elif created_from == "brain_automation_recipe" and metadata.get("recipe_id"):
|
|
347
|
-
by_recipe[str(metadata["recipe_id"])] = workflow
|
|
348
|
-
return by_suggestion, by_recipe
|
|
349
|
-
|
|
350
|
-
def suggestions(
|
|
351
|
-
self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
|
|
352
|
-
) -> Dict[str, Any]:
|
|
353
|
-
pattern_report = self.question_patterns(user_email=user_email, workspace_id=workspace_id)
|
|
354
|
-
by_suggestion, by_recipe = self._installed_workflows(workspace_id=workspace_id)
|
|
355
|
-
|
|
356
|
-
items: List[Dict[str, Any]] = []
|
|
357
|
-
suppressed_low_confidence = 0
|
|
358
|
-
suppressed_duplicates = 0
|
|
359
|
-
seen_recipe_ids: set = set()
|
|
360
|
-
|
|
361
|
-
# Patterns arrive sorted by (count, last_asked) desc, so the first
|
|
362
|
-
# suggestion per recipe is the strongest — later ones targeting the
|
|
363
|
-
# same recipe would install the identical workflow and are suppressed.
|
|
364
|
-
for pattern in pattern_report["patterns"]:
|
|
365
|
-
recipe_id = pattern["recipe_id"]
|
|
366
|
-
if recipe_id and recipe_id in seen_recipe_ids:
|
|
367
|
-
suppressed_duplicates += 1
|
|
368
|
-
continue
|
|
369
|
-
kg_related = self._kg_related_count(
|
|
370
|
-
pattern["representative"], workspace_id=workspace_id
|
|
371
|
-
)
|
|
372
|
-
confidence, factors = _question_confidence(
|
|
373
|
-
pattern["count"], pattern["examples"], recipe_id, kg_related
|
|
374
|
-
)
|
|
375
|
-
if confidence < _MIN_SUGGESTION_CONFIDENCE:
|
|
376
|
-
# Asked a couple of times, phrased one way, matching no recipe,
|
|
377
|
-
# and the Brain knows nothing about it — not enough to propose
|
|
378
|
-
# automating. Counted, so the surface can say how many were held
|
|
379
|
-
# back rather than silently showing fewer.
|
|
380
|
-
suppressed_low_confidence += 1
|
|
381
|
-
continue
|
|
382
|
-
if recipe_id:
|
|
383
|
-
seen_recipe_ids.add(recipe_id)
|
|
384
|
-
suggestion_id = _stable_id("sug-q", pattern["id"])
|
|
385
|
-
workflow = by_suggestion.get(suggestion_id) or (
|
|
386
|
-
by_recipe.get(str(recipe_id)) if recipe_id else None
|
|
387
|
-
)
|
|
388
|
-
items.append({
|
|
389
|
-
"id": suggestion_id,
|
|
390
|
-
"kind": "recurring_question",
|
|
391
|
-
"intent": pattern["intent"],
|
|
392
|
-
"recipe_id": recipe_id,
|
|
393
|
-
"title": pattern["representative"],
|
|
394
|
-
"reason": {
|
|
395
|
-
"type": "repeated_question",
|
|
396
|
-
"count": pattern["count"],
|
|
397
|
-
"last_asked": pattern["last_asked"],
|
|
398
|
-
"examples": pattern["examples"],
|
|
399
|
-
},
|
|
400
|
-
"cadence": "daily",
|
|
401
|
-
"confidence": confidence,
|
|
402
|
-
"confidence_factors": factors,
|
|
403
|
-
"low_confidence": confidence < _LOW_CONFIDENCE_THRESHOLD,
|
|
404
|
-
"installed": workflow is not None,
|
|
405
|
-
"workflow_id": (workflow or {}).get("id"),
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
for source in self._knowledge_sources():
|
|
409
|
-
file_status = source.get("file_status") or {}
|
|
410
|
-
indexed = sum(int(v or 0) for v in file_status.values())
|
|
411
|
-
if indexed <= 0:
|
|
412
|
-
continue
|
|
413
|
-
confidence, factors = _source_confidence(
|
|
414
|
-
indexed, bool(source.get("watch_enabled"))
|
|
415
|
-
)
|
|
416
|
-
if confidence < _MIN_SUGGESTION_CONFIDENCE:
|
|
417
|
-
# A barely-indexed folder is not enough evidence for a digest
|
|
418
|
-
# automation yet — do not suggest.
|
|
419
|
-
suppressed_low_confidence += 1
|
|
420
|
-
continue
|
|
421
|
-
suggestion_id = _stable_id("sug-src", str(source.get("id") or source.get("root_path") or ""))
|
|
422
|
-
items.append({
|
|
423
|
-
"id": suggestion_id,
|
|
424
|
-
"kind": "knowledge_source",
|
|
425
|
-
"intent": "source_digest",
|
|
426
|
-
"recipe_id": None,
|
|
427
|
-
"title": str(source.get("label") or source.get("root_path") or "knowledge folder"),
|
|
428
|
-
"reason": {
|
|
429
|
-
"type": "connected_source",
|
|
430
|
-
"root_path": source.get("root_path"),
|
|
431
|
-
"indexed_files": indexed,
|
|
432
|
-
"watch_enabled": bool(source.get("watch_enabled")),
|
|
433
|
-
},
|
|
434
|
-
"cadence": "when new knowledge arrives",
|
|
435
|
-
"confidence": confidence,
|
|
436
|
-
"confidence_factors": factors,
|
|
437
|
-
"low_confidence": confidence < _LOW_CONFIDENCE_THRESHOLD,
|
|
438
|
-
"installed": suggestion_id in by_suggestion,
|
|
439
|
-
"workflow_id": (by_suggestion.get(suggestion_id) or {}).get("id"),
|
|
440
|
-
})
|
|
441
|
-
|
|
442
|
-
return {
|
|
443
|
-
"suggestions": items,
|
|
444
|
-
"questions_scanned": pattern_report["questions_scanned"],
|
|
445
|
-
"quality": {
|
|
446
|
-
"min_confidence": _MIN_SUGGESTION_CONFIDENCE,
|
|
447
|
-
"low_confidence_threshold": _LOW_CONFIDENCE_THRESHOLD,
|
|
448
|
-
"suppressed_low_confidence": suppressed_low_confidence,
|
|
449
|
-
"suppressed_duplicates": suppressed_duplicates,
|
|
450
|
-
},
|
|
451
|
-
"consent": {
|
|
452
|
-
"default_state": "draft_disabled",
|
|
453
|
-
"local_only": True,
|
|
454
|
-
"external_actions": False,
|
|
455
|
-
"requires_user_enable": True,
|
|
456
|
-
"review_before_run": True,
|
|
457
|
-
},
|
|
458
|
-
"generated_at": _now(),
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
def find_suggestion(
|
|
462
|
-
self,
|
|
463
|
-
suggestion_id: str,
|
|
464
|
-
*,
|
|
465
|
-
user_email: Optional[str] = None,
|
|
466
|
-
workspace_id: Optional[str] = None,
|
|
467
|
-
) -> Optional[Dict[str, Any]]:
|
|
468
|
-
for suggestion in self.suggestions(user_email=user_email, workspace_id=workspace_id)["suggestions"]:
|
|
469
|
-
if suggestion["id"] == suggestion_id:
|
|
470
|
-
return suggestion
|
|
471
|
-
return None
|
|
472
|
-
|
|
473
|
-
# ── workflow building ────────────────────────────────────────────────
|
|
474
|
-
|
|
475
|
-
def build_suggestion_workflow(
|
|
476
|
-
self, suggestion: Dict[str, Any], *, enabled: bool = False
|
|
477
|
-
) -> Dict[str, Any]:
|
|
478
|
-
"""Build a consent-first workflow definition from a suggestion.
|
|
479
|
-
|
|
480
|
-
Mirrors the starter-recipe shape: trigger → draft agent → review
|
|
481
|
-
output, installed disabled by default, review-queue gated, local
|
|
482
|
-
only, and stamped with provenance so installs stay idempotent.
|
|
483
|
-
"""
|
|
484
|
-
kind = suggestion.get("kind")
|
|
485
|
-
title = str(suggestion.get("title") or "automation")[:80]
|
|
486
|
-
if kind == "knowledge_source":
|
|
487
|
-
trigger: Dict[str, Any] = {"trigger": "brain_event"}
|
|
488
|
-
trigger_name = "New knowledge in connected folder"
|
|
489
|
-
name = f"Folder digest: {title}"
|
|
490
|
-
reason = suggestion.get("reason") or {}
|
|
491
|
-
prompt = (
|
|
492
|
-
"New knowledge arrived from the connected folder "
|
|
493
|
-
f"'{reason.get('root_path') or title}'. Draft a short digest of what "
|
|
494
|
-
"was added, what changed, and any follow-ups it suggests. Use only "
|
|
495
|
-
"the local Brain; do not contact external services."
|
|
496
|
-
)
|
|
497
|
-
creates = ["folder digest", "change summary", "follow-up suggestions"]
|
|
498
|
-
else:
|
|
499
|
-
trigger = {"trigger": "interval", "interval_seconds": 86_400}
|
|
500
|
-
trigger_name = "User-enabled schedule"
|
|
501
|
-
name = f"Scheduled answer: {title}"
|
|
502
|
-
prompt = (
|
|
503
|
-
"The user repeatedly asks this question:\n"
|
|
504
|
-
f"“{suggestion.get('title')}”\n"
|
|
505
|
-
"Answer it from the current Brain (memories, knowledge graph, "
|
|
506
|
-
"recent activity) as a concise draft the user can review. "
|
|
507
|
-
"Use only local knowledge; do not contact external services."
|
|
508
|
-
)
|
|
509
|
-
creates = ["scheduled answer draft", "supporting evidence", "suggested follow-ups"]
|
|
510
|
-
|
|
511
|
-
trigger_config = {
|
|
512
|
-
**trigger,
|
|
513
|
-
"enabled": bool(enabled),
|
|
514
|
-
"review_queue": True,
|
|
515
|
-
"consent_required": True,
|
|
516
|
-
"local_only": True,
|
|
517
|
-
"external_actions": False,
|
|
518
|
-
}
|
|
519
|
-
return {
|
|
520
|
-
"name": name,
|
|
521
|
-
"nodes": [
|
|
522
|
-
{
|
|
523
|
-
"id": "trigger",
|
|
524
|
-
"type": "trigger",
|
|
525
|
-
"name": trigger_name,
|
|
526
|
-
"config": trigger_config,
|
|
527
|
-
"next": "draft",
|
|
528
|
-
},
|
|
529
|
-
{
|
|
530
|
-
"id": "draft",
|
|
531
|
-
"type": "agent",
|
|
532
|
-
"name": "Draft for review",
|
|
533
|
-
"config": {
|
|
534
|
-
"agent": "agent:planner",
|
|
535
|
-
"goal": prompt,
|
|
536
|
-
"prompt": prompt,
|
|
537
|
-
"roles": ["researcher", "planner", "executor", "reviewer"],
|
|
538
|
-
"mode": "draft",
|
|
539
|
-
"local_only": True,
|
|
540
|
-
"external_actions": False,
|
|
541
|
-
"requires_review": True,
|
|
542
|
-
},
|
|
543
|
-
"next": "output",
|
|
544
|
-
},
|
|
545
|
-
{
|
|
546
|
-
"id": "output",
|
|
547
|
-
"type": "output",
|
|
548
|
-
"name": "Review before saving",
|
|
549
|
-
"config": {
|
|
550
|
-
"value": "Draft ready for review. Save, edit, or discard it before it becomes durable memory.",
|
|
551
|
-
},
|
|
552
|
-
"next": None,
|
|
553
|
-
},
|
|
554
|
-
],
|
|
555
|
-
"metadata": {
|
|
556
|
-
"created_from": "automation_suggestion",
|
|
557
|
-
"suggestion_id": suggestion.get("id"),
|
|
558
|
-
"suggestion_kind": kind,
|
|
559
|
-
"suggestion_title": title,
|
|
560
|
-
"suggestion_reason": suggestion.get("reason"),
|
|
561
|
-
"suggestion_confidence": suggestion.get("confidence"),
|
|
562
|
-
"automation_state": "enabled" if enabled else "draft_disabled",
|
|
563
|
-
"local_only": True,
|
|
564
|
-
"external_actions": False,
|
|
565
|
-
"requires_user_enable": not enabled,
|
|
566
|
-
"creates": creates,
|
|
567
|
-
},
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
# ── overview for the automation surface ──────────────────────────────
|
|
571
|
-
|
|
572
|
-
def overview(
|
|
573
|
-
self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
|
|
574
|
-
) -> Dict[str, Any]:
|
|
575
|
-
suggestion_report = self.suggestions(user_email=user_email, workspace_id=workspace_id)
|
|
576
|
-
installed: List[Dict[str, Any]] = []
|
|
577
|
-
if self._store is not None:
|
|
578
|
-
try:
|
|
579
|
-
workflows = self._store.list_workflows(workspace_id=workspace_id).get("workflows") or []
|
|
580
|
-
except Exception:
|
|
581
|
-
LOGGER.exception("automation intelligence workflow read failed")
|
|
582
|
-
workflows = []
|
|
583
|
-
from latticeai.services.automation_execution import last_execution_view
|
|
584
|
-
|
|
585
|
-
for workflow in workflows:
|
|
586
|
-
metadata = (workflow or {}).get("metadata") or {}
|
|
587
|
-
if metadata.get("created_from") not in {"automation_suggestion", "brain_automation_recipe"}:
|
|
588
|
-
continue
|
|
589
|
-
installed.append({
|
|
590
|
-
"id": workflow.get("id"),
|
|
591
|
-
"name": workflow.get("name"),
|
|
592
|
-
"created_from": metadata.get("created_from"),
|
|
593
|
-
"suggestion_id": metadata.get("suggestion_id"),
|
|
594
|
-
"recipe_id": metadata.get("recipe_id"),
|
|
595
|
-
"enabled": metadata.get("automation_state") == "enabled",
|
|
596
|
-
"requires_user_enable": bool(metadata.get("requires_user_enable", True)),
|
|
597
|
-
"creates": metadata.get("creates") or [],
|
|
598
|
-
# Execution log surfacing (backlog #6): the newest of the
|
|
599
|
-
# run-now stamp vs. the latest persisted workflow run.
|
|
600
|
-
"last_execution": last_execution_view(
|
|
601
|
-
workflow, store=self._store, workspace_id=workspace_id
|
|
602
|
-
),
|
|
603
|
-
})
|
|
604
|
-
return {
|
|
605
|
-
"suggestions": suggestion_report["suggestions"],
|
|
606
|
-
"questions_scanned": suggestion_report["questions_scanned"],
|
|
607
|
-
"installed": installed,
|
|
608
|
-
"quality": suggestion_report["quality"],
|
|
609
|
-
"consent": suggestion_report["consent"],
|
|
610
|
-
"generated_at": _now(),
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
__all__ = ["AutomationIntelligenceService"]
|