ltcai 11.5.2 → 11.6.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 +91 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +75 -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 +21 -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-CToZnOHz.js} +1 -1
- package/static/app/assets/{AdminConsole-cf4npybT.js → AdminConsole-D5kbrBu8.js} +1 -1
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-C6zCdv4S.js} +1 -1
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-D4n3EDUt.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-CzeZ-vQ9.js} +1 -1
- package/static/app/assets/{Capture-DiQ219jW.js → Capture-DPcyTczq.js} +1 -1
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-D3Nx-oWS.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-DPPvpp28.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-Co2qTu3I.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-Bsmzwy8_.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-CBLdmesT.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-CfR6DLs7.js} +1 -1
- package/static/app/assets/{System-CieofHQa.js → System-Ce-P2YL6.js} +1 -1
- package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-C5-XIww2.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CFRJUvdX.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-DI1KcQP-.js} +1 -1
- package/static/app/assets/circle-check-CY20XMCh.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-C8HHyood.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-Cufc235E.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DW2_JVsv.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-gbdKTBb8.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-C9ydyX33.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-BxNMzBM3.js} +1 -1
- package/static/app/assets/index-BovSWRmQ.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-q9XyDY7A.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-GLD94Y_K.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-B4Y0gz1S.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-CxrV0fvU.js} +1 -1
- package/static/app/assets/{primitives-SNp0LRJz.js → primitives-2ANdAsu2.js} +1 -1
- package/static/app/assets/search-DTxtjQ07.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-DxArfiuI.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-CgftuFFs.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-BHVBAoCr.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-Dzq2j_OG.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-BcqfMLNq.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-DBzLKCOv.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-B-Aah1bO.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-B6fFhoko.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/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/search-BcHqkjoy.js +0 -1
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
"""Relationship explorer: what a node connects to, and the way between two.
|
|
2
|
-
|
|
3
|
-
Extracted from ``WorkspaceOSStore``. Pure graph reading — it touches no state
|
|
4
|
-
file and records no events, so it holds no reference to the store at all.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from collections import deque
|
|
10
|
-
from typing import Any, Dict, List, Optional
|
|
11
|
-
|
|
12
|
-
from latticeai.core.quiet import quiet
|
|
13
|
-
|
|
14
|
-
__all__ = ["WorkspaceRelationships", "shortest_path"]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def shortest_path(edges: List[Dict[str, Any]], start: str, target: Optional[str]) -> List[str]:
|
|
18
|
-
"""Fewest-hops path between two nodes, treating every edge as undirected.
|
|
19
|
-
|
|
20
|
-
Breadth-first, so the first arrival at ``target`` is a shortest one.
|
|
21
|
-
Returns ``[]`` when either end is missing or nothing connects them.
|
|
22
|
-
"""
|
|
23
|
-
if not start or not target:
|
|
24
|
-
return []
|
|
25
|
-
adjacency: Dict[str, List[str]] = {}
|
|
26
|
-
for edge in edges:
|
|
27
|
-
src = edge.get("from")
|
|
28
|
-
dst = edge.get("to")
|
|
29
|
-
if src and dst:
|
|
30
|
-
adjacency.setdefault(src, []).append(dst)
|
|
31
|
-
adjacency.setdefault(dst, []).append(src)
|
|
32
|
-
queue: deque[List[str]] = deque([[start]])
|
|
33
|
-
seen = {start}
|
|
34
|
-
while queue:
|
|
35
|
-
path = queue.popleft()
|
|
36
|
-
node = path[-1]
|
|
37
|
-
if node == target:
|
|
38
|
-
return path
|
|
39
|
-
for neighbor in adjacency.get(node, []):
|
|
40
|
-
if neighbor not in seen:
|
|
41
|
-
seen.add(neighbor)
|
|
42
|
-
queue.append(path + [neighbor])
|
|
43
|
-
return []
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class WorkspaceRelationships:
|
|
47
|
-
"""Answers "what is this connected to" for one node of the graph."""
|
|
48
|
-
|
|
49
|
-
def __init__(self, store: Any) -> None:
|
|
50
|
-
self.store = store
|
|
51
|
-
|
|
52
|
-
def explore(
|
|
53
|
-
self,
|
|
54
|
-
graph: Any,
|
|
55
|
-
node_id: str,
|
|
56
|
-
target_id: Optional[str] = None,
|
|
57
|
-
limit: int = 500,
|
|
58
|
-
) -> Dict[str, Any]:
|
|
59
|
-
if graph is None:
|
|
60
|
-
return {
|
|
61
|
-
"node_id": node_id,
|
|
62
|
-
"inbound": [],
|
|
63
|
-
"outbound": [],
|
|
64
|
-
"related_entities": [],
|
|
65
|
-
"shortest_path": [],
|
|
66
|
-
}
|
|
67
|
-
data = graph.graph(limit=limit)
|
|
68
|
-
nodes = {node.get("id"): node for node in data.get("nodes") or [] if node.get("id")}
|
|
69
|
-
edges = data.get("edges") or []
|
|
70
|
-
inbound = [edge for edge in edges if edge.get("to") == node_id]
|
|
71
|
-
outbound = [edge for edge in edges if edge.get("from") == node_id]
|
|
72
|
-
# The capped `graph(limit=…)` window may not contain this node at all.
|
|
73
|
-
# Asking the store for its neighbours directly is the difference between
|
|
74
|
-
# "not connected" and "outside the page we happened to fetch".
|
|
75
|
-
if node_id not in nodes:
|
|
76
|
-
try:
|
|
77
|
-
neighbors = graph.neighbors(node_id)
|
|
78
|
-
for node in neighbors.get("neighbors") or []:
|
|
79
|
-
nodes[node.get("id")] = node
|
|
80
|
-
edges.extend(neighbors.get("edges") or [])
|
|
81
|
-
inbound = [edge for edge in edges if edge.get("to") == node_id]
|
|
82
|
-
outbound = [edge for edge in edges if edge.get("from") == node_id]
|
|
83
|
-
except Exception:
|
|
84
|
-
quiet()
|
|
85
|
-
|
|
86
|
-
related_ids = []
|
|
87
|
-
for edge in inbound + outbound:
|
|
88
|
-
other = edge.get("from") if edge.get("to") == node_id else edge.get("to")
|
|
89
|
-
if other:
|
|
90
|
-
related_ids.append(other)
|
|
91
|
-
related = [nodes.get(rid, {"id": rid}) for rid in dict.fromkeys(related_ids)]
|
|
92
|
-
return {
|
|
93
|
-
"node_id": node_id,
|
|
94
|
-
"node": nodes.get(node_id, {"id": node_id}),
|
|
95
|
-
"inbound": inbound,
|
|
96
|
-
"outbound": outbound,
|
|
97
|
-
"related_entities": related,
|
|
98
|
-
"shortest_path": shortest_path(edges, node_id, target_id) if target_id else [],
|
|
99
|
-
}
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
"""Agent-native workspace reorganization — proposals, never a silent tidy-up.
|
|
2
|
-
|
|
3
|
-
"이 프로젝트를 정리해줘" is a request an agent can now answer without being
|
|
4
|
-
trusted with the filesystem. The Brain looks at a folder, asks the graph what
|
|
5
|
-
each file is *about*, and stages one review proposal describing every move it
|
|
6
|
-
would make. Nothing on disk changes until a person approves it, and approving
|
|
7
|
-
applies exactly the moves that were reviewed.
|
|
8
|
-
|
|
9
|
-
Three rules make this safe enough to hand to a long-running session:
|
|
10
|
-
|
|
11
|
-
* **No deletions, ever.** The planner emits moves only. There is no code path
|
|
12
|
-
here that removes a file, so the worst outcome of a bad proposal is a file
|
|
13
|
-
in a folder you did not want (recoverable) rather than a file that is gone
|
|
14
|
-
(not). Plan §10's conservative mitigation, made structural.
|
|
15
|
-
* **Only what the Brain actually knows.** A file is moved because the graph
|
|
16
|
-
links it to a topic — never because of a guess from its extension. Files the
|
|
17
|
-
Brain has nothing to say about are reported as ``unplaced`` with a reason
|
|
18
|
-
instead of being swept somewhere plausible.
|
|
19
|
-
* **Deterministic.** The same folder and the same graph produce the same plan,
|
|
20
|
-
in the same order: candidates are sorted, topics are chosen by
|
|
21
|
-
(weight, title), and collisions resolve to the first path alphabetically.
|
|
22
|
-
|
|
23
|
-
Every path in and out of this module is workspace-relative and resolved
|
|
24
|
-
through the caller's sandboxed ``resolve_path`` (the agent-root resolver), so
|
|
25
|
-
a proposal can never name a target outside the workspace.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
from __future__ import annotations
|
|
29
|
-
|
|
30
|
-
import logging
|
|
31
|
-
import os
|
|
32
|
-
import re
|
|
33
|
-
from pathlib import Path
|
|
34
|
-
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
35
|
-
|
|
36
|
-
from latticeai.core.timeutil import now_iso as _now
|
|
37
|
-
|
|
38
|
-
LOGGER = logging.getLogger(__name__)
|
|
39
|
-
|
|
40
|
-
#: Review-queue kind for a reorganization proposal. Source stays
|
|
41
|
-
#: ``change_proposal`` so the Review Center's approve path applies it through
|
|
42
|
-
#: :class:`~latticeai.services.change_proposals.ChangeProposalService`.
|
|
43
|
-
REORG_KIND = "folder_reorganization"
|
|
44
|
-
|
|
45
|
-
#: Folder the plan groups into. One level, named after the topic, so the result
|
|
46
|
-
#: is legible without a map.
|
|
47
|
-
TOPIC_ROOT = "topics"
|
|
48
|
-
|
|
49
|
-
#: Node types that stand for a file on disk, and the ones that stand for what a
|
|
50
|
-
#: file is about.
|
|
51
|
-
FILE_NODE_TYPES = frozenset(
|
|
52
|
-
{
|
|
53
|
-
"File", "CodeFile", "Document", "Image", "Spreadsheet", "SlideDeck",
|
|
54
|
-
"ImageText", "Audio",
|
|
55
|
-
}
|
|
56
|
-
)
|
|
57
|
-
TOPIC_NODE_TYPES = frozenset({"Topic", "Concept", "Project", "Feature"})
|
|
58
|
-
|
|
59
|
-
DEFAULT_MAX_MOVES = 20
|
|
60
|
-
DEFAULT_SCAN_LIMIT = 400
|
|
61
|
-
DEFAULT_GRAPH_LIMIT = 800
|
|
62
|
-
|
|
63
|
-
_SLUG_RE = re.compile(r"[^0-9A-Za-z가-힣._-]+")
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _slug(value: str) -> str:
|
|
67
|
-
"""Folder-safe topic name (Korean kept — the user reads these folders)."""
|
|
68
|
-
return _SLUG_RE.sub("-", str(value or "").strip()).strip("-.").lower()[:48]
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def _rel(path: Path, base: Path) -> str:
|
|
72
|
-
return path.relative_to(base).as_posix()
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _scan(base: Path, limit: int) -> List[Path]:
|
|
76
|
-
"""Visible files under ``base``, deepest-stable order, capped at ``limit``."""
|
|
77
|
-
found: List[Path] = []
|
|
78
|
-
for path in sorted(base.rglob("*")):
|
|
79
|
-
if len(found) >= limit:
|
|
80
|
-
break
|
|
81
|
-
if not path.is_file():
|
|
82
|
-
continue
|
|
83
|
-
if any(part.startswith(".") for part in path.relative_to(base).parts):
|
|
84
|
-
continue
|
|
85
|
-
found.append(path)
|
|
86
|
-
return found
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def _file_node_index(graph: Any, limit: int) -> Tuple[Dict[str, str], str]:
|
|
90
|
-
"""``{file name → graph node id}`` plus the basis for the plan.
|
|
91
|
-
|
|
92
|
-
A missing or unreadable graph yields an empty index and a basis that says
|
|
93
|
-
so, which is what makes an empty plan honest rather than mysterious.
|
|
94
|
-
"""
|
|
95
|
-
if graph is None:
|
|
96
|
-
return {}, "no_graph"
|
|
97
|
-
try:
|
|
98
|
-
data = graph.graph(limit)
|
|
99
|
-
except Exception: # noqa: BLE001 — a broken read proposes nothing
|
|
100
|
-
LOGGER.exception("workspace reorganization graph read failed")
|
|
101
|
-
return {}, "graph_unavailable"
|
|
102
|
-
index: Dict[str, str] = {}
|
|
103
|
-
for node in data.get("nodes") or []:
|
|
104
|
-
if str(node.get("type")) not in FILE_NODE_TYPES:
|
|
105
|
-
continue
|
|
106
|
-
for key in _file_keys(node):
|
|
107
|
-
index.setdefault(key, str(node.get("id")))
|
|
108
|
-
return index, "graph"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _topic_for(graph: Any, node_id: str) -> str:
|
|
112
|
-
"""The topic a file node belongs to, or ``""`` when the Brain has none.
|
|
113
|
-
|
|
114
|
-
One hop, strongest edge first; ties break on the topic title so the same
|
|
115
|
-
graph always produces the same folder.
|
|
116
|
-
"""
|
|
117
|
-
try:
|
|
118
|
-
data = graph.neighbors(node_id)
|
|
119
|
-
except Exception: # noqa: BLE001 — one unreadable node is not a failure
|
|
120
|
-
LOGGER.exception("workspace reorganization neighbour read failed")
|
|
121
|
-
return ""
|
|
122
|
-
by_id = {str(node.get("id")): node for node in data.get("neighbors") or []}
|
|
123
|
-
best: Optional[Tuple[float, str]] = None
|
|
124
|
-
for edge in data.get("edges") or []:
|
|
125
|
-
far = edge.get("to") if str(edge.get("from")) == node_id else edge.get("from")
|
|
126
|
-
node = by_id.get(str(far))
|
|
127
|
-
if node is None or str(node.get("type")) not in TOPIC_NODE_TYPES:
|
|
128
|
-
continue
|
|
129
|
-
title = str(node.get("title") or "").strip()
|
|
130
|
-
if not title:
|
|
131
|
-
continue
|
|
132
|
-
candidate = (-float(edge.get("weight") or 1.0), title)
|
|
133
|
-
if best is None or candidate < best:
|
|
134
|
-
best = candidate
|
|
135
|
-
return best[1] if best is not None else ""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def _file_keys(node: Dict[str, Any]) -> List[str]:
|
|
139
|
-
"""Every name a graph file node might be matched by (lowercased)."""
|
|
140
|
-
metadata = node.get("metadata") or {}
|
|
141
|
-
candidates = [
|
|
142
|
-
metadata.get("relative_path"),
|
|
143
|
-
metadata.get("filename"),
|
|
144
|
-
metadata.get("path"),
|
|
145
|
-
node.get("title"),
|
|
146
|
-
]
|
|
147
|
-
keys: List[str] = []
|
|
148
|
-
for candidate in candidates:
|
|
149
|
-
text = str(candidate or "").strip()
|
|
150
|
-
if not text:
|
|
151
|
-
continue
|
|
152
|
-
for key in (text.lower(), Path(text).name.lower()):
|
|
153
|
-
if key and key not in keys:
|
|
154
|
-
keys.append(key)
|
|
155
|
-
return keys
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def plan_reorganization(
|
|
159
|
-
*,
|
|
160
|
-
root: str = "",
|
|
161
|
-
resolve_path: Callable[[str], Path],
|
|
162
|
-
graph: Any = None,
|
|
163
|
-
max_moves: int = DEFAULT_MAX_MOVES,
|
|
164
|
-
scan_limit: int = DEFAULT_SCAN_LIMIT,
|
|
165
|
-
graph_limit: int = DEFAULT_GRAPH_LIMIT,
|
|
166
|
-
) -> Dict[str, Any]:
|
|
167
|
-
"""What the Brain would move, and what it deliberately would not.
|
|
168
|
-
|
|
169
|
-
Read-only: this touches no file and creates no proposal.
|
|
170
|
-
"""
|
|
171
|
-
base = resolve_path(root)
|
|
172
|
-
if not base.is_dir():
|
|
173
|
-
return _empty_plan(root, "folder_missing")
|
|
174
|
-
index, basis = _file_node_index(graph, graph_limit)
|
|
175
|
-
moves: List[Dict[str, Any]] = []
|
|
176
|
-
unplaced: List[Dict[str, Any]] = []
|
|
177
|
-
claimed: set = set()
|
|
178
|
-
for path in _scan(base, scan_limit):
|
|
179
|
-
source = _rel(path, base)
|
|
180
|
-
node_id = index.get(source.lower()) or index.get(path.name.lower())
|
|
181
|
-
topic = _topic_for(graph, node_id) if node_id else ""
|
|
182
|
-
if not topic:
|
|
183
|
-
unplaced.append({"path": source, "reason": "brain_has_no_topic"})
|
|
184
|
-
continue
|
|
185
|
-
target = f"{TOPIC_ROOT}/{_slug(topic)}/{path.name}"
|
|
186
|
-
if target == source:
|
|
187
|
-
unplaced.append({"path": source, "reason": "already_in_place"})
|
|
188
|
-
continue
|
|
189
|
-
if target in claimed or (base / target).exists():
|
|
190
|
-
unplaced.append({"path": source, "reason": "target_taken"})
|
|
191
|
-
continue
|
|
192
|
-
claimed.add(target)
|
|
193
|
-
moves.append({
|
|
194
|
-
"source": source,
|
|
195
|
-
"target": target,
|
|
196
|
-
"topic": topic,
|
|
197
|
-
"reason": f"'{topic}' 주제와 이어져 있습니다",
|
|
198
|
-
})
|
|
199
|
-
trimmed = len(moves) > max_moves
|
|
200
|
-
return {
|
|
201
|
-
"available": True,
|
|
202
|
-
"root": root,
|
|
203
|
-
"basis": basis,
|
|
204
|
-
"moves": moves[: max(0, int(max_moves))],
|
|
205
|
-
"move_count": len(moves[: max(0, int(max_moves))]),
|
|
206
|
-
"truncated": trimmed,
|
|
207
|
-
"unplaced": unplaced,
|
|
208
|
-
"unplaced_count": len(unplaced),
|
|
209
|
-
# Structural, not a policy toggle: this planner has no delete path.
|
|
210
|
-
"deletes": [],
|
|
211
|
-
"generated_at": _now(),
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def _empty_plan(root: str, reason: str) -> Dict[str, Any]:
|
|
216
|
-
return {
|
|
217
|
-
"available": False,
|
|
218
|
-
"root": root,
|
|
219
|
-
"basis": reason,
|
|
220
|
-
"moves": [],
|
|
221
|
-
"move_count": 0,
|
|
222
|
-
"truncated": False,
|
|
223
|
-
"unplaced": [],
|
|
224
|
-
"unplaced_count": 0,
|
|
225
|
-
"deletes": [],
|
|
226
|
-
"generated_at": _now(),
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def propose_reorganization(
|
|
231
|
-
*,
|
|
232
|
-
root: str = "",
|
|
233
|
-
resolve_path: Callable[[str], Path],
|
|
234
|
-
review_queue: Any,
|
|
235
|
-
graph: Any = None,
|
|
236
|
-
user_email: Optional[str] = None,
|
|
237
|
-
workspace_id: Optional[str] = None,
|
|
238
|
-
max_moves: int = DEFAULT_MAX_MOVES,
|
|
239
|
-
audit: Optional[Callable[..., None]] = None,
|
|
240
|
-
) -> Dict[str, Any]:
|
|
241
|
-
"""Stage one review proposal describing the whole reorganization.
|
|
242
|
-
|
|
243
|
-
Returns ``{"proposed": None, ...}`` when there is nothing to propose — an
|
|
244
|
-
empty proposal is noise, and a Brain that knows nothing about a folder
|
|
245
|
-
should say so rather than invent a structure.
|
|
246
|
-
"""
|
|
247
|
-
plan = plan_reorganization(
|
|
248
|
-
root=root, resolve_path=resolve_path, graph=graph, max_moves=max_moves
|
|
249
|
-
)
|
|
250
|
-
if not plan["moves"]:
|
|
251
|
-
return {"proposed": None, "plan": plan, "reason": plan["basis"]}
|
|
252
|
-
preview = ", ".join(move["source"] for move in plan["moves"][:3])
|
|
253
|
-
item = review_queue.create(
|
|
254
|
-
title=f"폴더 정리 제안: {root or '작업 폴더'} 파일 {plan['move_count']}개",
|
|
255
|
-
summary=(
|
|
256
|
-
f"{preview} 등 {plan['move_count']}개 파일을 주제별 폴더로 옮기는 제안입니다. "
|
|
257
|
-
"삭제는 제안하지 않으며, 승인하기 전에는 아무것도 움직이지 않습니다."
|
|
258
|
-
),
|
|
259
|
-
source="change_proposal",
|
|
260
|
-
kind=REORG_KIND,
|
|
261
|
-
payload={
|
|
262
|
-
"root": root,
|
|
263
|
-
"moves": plan["moves"],
|
|
264
|
-
"unplaced": plan["unplaced"],
|
|
265
|
-
"basis": plan["basis"],
|
|
266
|
-
"contract": {"moves": "proposal", "deletions": "never_proposed"},
|
|
267
|
-
},
|
|
268
|
-
provenance={
|
|
269
|
-
"proposed_by": "agent",
|
|
270
|
-
"reason": "workspace reorganization",
|
|
271
|
-
"source_detail": "brain topic grouping",
|
|
272
|
-
},
|
|
273
|
-
user_email=user_email,
|
|
274
|
-
workspace_id=workspace_id,
|
|
275
|
-
)
|
|
276
|
-
if audit is not None:
|
|
277
|
-
audit(
|
|
278
|
-
"workspace_reorg_proposed",
|
|
279
|
-
user_email=user_email,
|
|
280
|
-
proposal_id=item.get("id"),
|
|
281
|
-
moves=plan["move_count"],
|
|
282
|
-
)
|
|
283
|
-
return {"proposed": item, "plan": plan, "reason": ""}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def apply_reorganization(
|
|
287
|
-
payload: Dict[str, Any], *, resolve_path: Callable[[str], Path]
|
|
288
|
-
) -> Dict[str, Any]:
|
|
289
|
-
"""Carry out the reviewed moves. Nothing is ever deleted or overwritten.
|
|
290
|
-
|
|
291
|
-
Each move is checked again at apply time: a source that vanished and a
|
|
292
|
-
target that appeared are both reported as skipped rather than forced, so a
|
|
293
|
-
stale proposal degrades into an honest partial result.
|
|
294
|
-
"""
|
|
295
|
-
applied: List[Dict[str, Any]] = []
|
|
296
|
-
skipped: List[Dict[str, Any]] = []
|
|
297
|
-
for move in payload.get("moves") or []:
|
|
298
|
-
source_rel = str(move.get("source") or "")
|
|
299
|
-
target_rel = str(move.get("target") or "")
|
|
300
|
-
if not source_rel or not target_rel:
|
|
301
|
-
skipped.append({"source": source_rel, "reason": "incomplete_move"})
|
|
302
|
-
continue
|
|
303
|
-
source = resolve_path(_join(payload, source_rel))
|
|
304
|
-
target = resolve_path(_join(payload, target_rel))
|
|
305
|
-
if not source.is_file():
|
|
306
|
-
skipped.append({"source": source_rel, "reason": "source_missing"})
|
|
307
|
-
continue
|
|
308
|
-
if target.exists():
|
|
309
|
-
skipped.append({"source": source_rel, "reason": "target_exists"})
|
|
310
|
-
continue
|
|
311
|
-
target.parent.mkdir(parents=True, exist_ok=True)
|
|
312
|
-
os.replace(source, target)
|
|
313
|
-
applied.append({"source": source_rel, "target": target_rel})
|
|
314
|
-
return {
|
|
315
|
-
"applied": applied,
|
|
316
|
-
"applied_count": len(applied),
|
|
317
|
-
"skipped": skipped,
|
|
318
|
-
"skipped_count": len(skipped),
|
|
319
|
-
"deleted": 0,
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
def _join(payload: Dict[str, Any], relative: str) -> str:
|
|
324
|
-
root = str(payload.get("root") or "").strip("/")
|
|
325
|
-
return f"{root}/{relative}" if root else relative
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
__all__ = [
|
|
329
|
-
"DEFAULT_MAX_MOVES",
|
|
330
|
-
"REORG_KIND",
|
|
331
|
-
"TOPIC_ROOT",
|
|
332
|
-
"apply_reorganization",
|
|
333
|
-
"plan_reorganization",
|
|
334
|
-
"propose_reorganization",
|
|
335
|
-
]
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
"""Review-queue persistence extracted from WorkspaceOSStore (v9.9.6).
|
|
2
|
-
|
|
3
|
-
Workspace-scoped suggestion inbox: automation/trigger runs write drafts here
|
|
4
|
-
for the user to approve/dismiss/snooze. **Persistence only** — the transition
|
|
5
|
-
policy lives in :mod:`latticeai.services.review_queue`.
|
|
6
|
-
|
|
7
|
-
Behaviour-preserving move (review 2026-07-27 P2 #8 "대형 모듈 추가 분해 —
|
|
8
|
-
동작 보존 이동 우선"): the store delegates the same method names to this
|
|
9
|
-
collaborator, following the existing ``WorkspaceRuns`` / ``WorkspaceGraphTrace``
|
|
10
|
-
pattern.
|
|
11
|
-
"""
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
from typing import Any, Dict, List, Optional
|
|
15
|
-
|
|
16
|
-
from .timeutil import now_iso as _now
|
|
17
|
-
from .workspace_os_utils import WorkspaceStoreMixin, _json_hash, _listify
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class WorkspaceReviewItems(WorkspaceStoreMixin):
|
|
21
|
-
def create_review_item(
|
|
22
|
-
self,
|
|
23
|
-
*,
|
|
24
|
-
title: str,
|
|
25
|
-
summary: str = "",
|
|
26
|
-
source: str = "workflow_run",
|
|
27
|
-
kind: str = "suggestion",
|
|
28
|
-
payload: Optional[Dict[str, Any]] = None,
|
|
29
|
-
provenance: Optional[Dict[str, Any]] = None,
|
|
30
|
-
user_email: Optional[str] = None,
|
|
31
|
-
workspace_id: Optional[str] = None,
|
|
32
|
-
) -> Dict[str, Any]:
|
|
33
|
-
if not str(title or "").strip():
|
|
34
|
-
raise ValueError("title is required")
|
|
35
|
-
state = self.load_state()
|
|
36
|
-
resolved_workspace = self._resolve_scope(workspace_id, state)
|
|
37
|
-
now = _now()
|
|
38
|
-
# ``now`` has second resolution, so two identical drafts written inside
|
|
39
|
-
# the same second would hash to the same id and the second one would be
|
|
40
|
-
# unreachable — ``get``/``update`` both return the first match. Rehash
|
|
41
|
-
# with a sequence suffix until the candidate is unique. The no-collision
|
|
42
|
-
# path is unchanged, so existing ids keep their bytes.
|
|
43
|
-
existing_ids = {it.get("id") for it in _listify(state.get("review_items"))}
|
|
44
|
-
item_id = f"review-{_json_hash([title, source, kind, user_email, now])[:16]}"
|
|
45
|
-
seq = 0
|
|
46
|
-
while item_id in existing_ids:
|
|
47
|
-
seq += 1
|
|
48
|
-
item_id = f"review-{_json_hash([title, source, kind, user_email, now, seq])[:16]}"
|
|
49
|
-
item = {
|
|
50
|
-
"id": item_id,
|
|
51
|
-
"status": "pending",
|
|
52
|
-
"title": title,
|
|
53
|
-
"summary": summary or "",
|
|
54
|
-
"source": source or "workflow_run",
|
|
55
|
-
"kind": kind or "suggestion",
|
|
56
|
-
"payload": dict(payload or {}),
|
|
57
|
-
"provenance": dict(provenance or {}),
|
|
58
|
-
"snoozed_until": None,
|
|
59
|
-
"user_email": user_email,
|
|
60
|
-
"workspace_id": resolved_workspace,
|
|
61
|
-
"created_at": now,
|
|
62
|
-
"updated_at": now,
|
|
63
|
-
}
|
|
64
|
-
state.setdefault("review_items", []).append(item)
|
|
65
|
-
self.save_state(state)
|
|
66
|
-
self.record_timeline_event(
|
|
67
|
-
"review", "review_item_created",
|
|
68
|
-
{"item_id": item["id"], "source": item["source"], "kind": item["kind"]},
|
|
69
|
-
workspace_id=resolved_workspace,
|
|
70
|
-
)
|
|
71
|
-
return item
|
|
72
|
-
|
|
73
|
-
def list_review_items(
|
|
74
|
-
self, *, workspace_id: Optional[str] = None, user_email: Optional[str] = None,
|
|
75
|
-
source: Optional[str] = None,
|
|
76
|
-
) -> List[Dict[str, Any]]:
|
|
77
|
-
items = self._scoped(_listify(self.load_state().get("review_items")), workspace_id)
|
|
78
|
-
if user_email:
|
|
79
|
-
items = [item for item in items if item.get("user_email") in {None, user_email}]
|
|
80
|
-
if source:
|
|
81
|
-
items = [item for item in items if item.get("source") == source]
|
|
82
|
-
return list(reversed(items))
|
|
83
|
-
|
|
84
|
-
def get_review_item(self, item_id: str, *, workspace_id: Optional[str] = None) -> Dict[str, Any]:
|
|
85
|
-
item = next(
|
|
86
|
-
(it for it in _listify(self.load_state().get("review_items")) if it.get("id") == item_id),
|
|
87
|
-
None,
|
|
88
|
-
)
|
|
89
|
-
if item is None or (workspace_id and self._record_workspace(item) != str(workspace_id)):
|
|
90
|
-
raise FileNotFoundError(item_id)
|
|
91
|
-
return item
|
|
92
|
-
|
|
93
|
-
def update_review_item(
|
|
94
|
-
self, item_id: str, *, workspace_id: Optional[str] = None, **fields: Any,
|
|
95
|
-
) -> Dict[str, Any]:
|
|
96
|
-
state = self.load_state()
|
|
97
|
-
item = next((it for it in _listify(state.get("review_items")) if it.get("id") == item_id), None)
|
|
98
|
-
if item is None or (workspace_id and self._record_workspace(item) != str(workspace_id)):
|
|
99
|
-
raise FileNotFoundError(item_id)
|
|
100
|
-
for key, value in fields.items():
|
|
101
|
-
item[key] = value
|
|
102
|
-
item["updated_at"] = _now()
|
|
103
|
-
self.save_state(state)
|
|
104
|
-
self.record_timeline_event(
|
|
105
|
-
"review", "review_item_updated",
|
|
106
|
-
{"item_id": item_id, "status": item.get("status")},
|
|
107
|
-
workspace_id=self._record_workspace(item),
|
|
108
|
-
)
|
|
109
|
-
return item
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
__all__ = ["WorkspaceReviewItems"]
|