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,474 +0,0 @@
|
|
|
1
|
-
"""Computer-use routes and the desktop-control agent loop."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import re
|
|
7
|
-
from typing import Any, Dict, List, Optional
|
|
8
|
-
|
|
9
|
-
from fastapi import APIRouter, HTTPException, Request
|
|
10
|
-
from fastapi.responses import StreamingResponse
|
|
11
|
-
from pydantic import BaseModel
|
|
12
|
-
|
|
13
|
-
from lattice_brain.runtime.hooks import dispatch_tool
|
|
14
|
-
from latticeai.api.workspace_scope import resolve_workspace_scope
|
|
15
|
-
from latticeai.core.agent import extract_action as _extract_agent_action
|
|
16
|
-
from latticeai.core.sse import sse_frame
|
|
17
|
-
from latticeai.services.tool_dispatch import enforce_tool_policy
|
|
18
|
-
from latticeai.tools import (
|
|
19
|
-
AGENT_ROOT,
|
|
20
|
-
ToolError,
|
|
21
|
-
computer_click,
|
|
22
|
-
computer_drag,
|
|
23
|
-
computer_key,
|
|
24
|
-
computer_move,
|
|
25
|
-
computer_open_app,
|
|
26
|
-
computer_open_url,
|
|
27
|
-
computer_screenshot,
|
|
28
|
-
computer_scroll,
|
|
29
|
-
computer_status,
|
|
30
|
-
computer_type,
|
|
31
|
-
desktop_bridge_status,
|
|
32
|
-
execute_tool,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
CU_SYSTEM_PROMPT = """You are Lattice AI desktop-control agent. You control the Mac desktop using tools.
|
|
36
|
-
Prefer non-visual direct actions when possible. Use screenshots only when you must inspect visible UI state or choose screen coordinates.
|
|
37
|
-
|
|
38
|
-
Available actions:
|
|
39
|
-
- computer_screenshot: {"action":"computer_screenshot","args":{}} — capture screen, returns screenshot_b64
|
|
40
|
-
- computer_open_app: {"action":"computer_open_app","args":{"app":"Google Chrome"}} — open or focus a Mac app
|
|
41
|
-
- computer_open_url: {"action":"computer_open_url","args":{"url":"https://example.com","app":"Google Chrome"}} — open URL in app
|
|
42
|
-
- computer_click: {"action":"computer_click","args":{"x":500,"y":300,"button":"left","double":false}}
|
|
43
|
-
- computer_type: {"action":"computer_type","args":{"text":"hello world","interval":0.04}}
|
|
44
|
-
- computer_key: {"action":"computer_key","args":{"key":"return"}} — keys: return, escape, tab, space, command+c, etc.
|
|
45
|
-
- computer_scroll: {"action":"computer_scroll","args":{"x":500,"y":300,"direction":"down","clicks":3}}
|
|
46
|
-
- computer_move: {"action":"computer_move","args":{"x":500,"y":300}}
|
|
47
|
-
- computer_drag: {"action":"computer_drag","args":{"x1":100,"y1":100,"x2":500,"y2":500}}
|
|
48
|
-
- vision_analyze: {"action":"vision_analyze","args":{"image_b64": "...", "prompt": "What do you see on screen?"}} — use after screenshot to let VLM describe/answer about the image (only when multimodal model loaded)
|
|
49
|
-
- final: {"action":"final","message":"Korean summary of what was accomplished"}
|
|
50
|
-
|
|
51
|
-
Rules:
|
|
52
|
-
- Respond with exactly ONE JSON object. No markdown, no extra text.
|
|
53
|
-
- Do not take screenshots for simple app launch, URL opening, keyboard shortcuts, or non-visual tasks.
|
|
54
|
-
- Take a screenshot before coordinate-based clicks/drags or when the task explicitly asks you to inspect the screen.
|
|
55
|
-
- After screenshot, prefer vision_analyze (with good prompt) over raw b64 in next step when you need to understand what is on screen (especially with multimodal/VLM loaded).
|
|
56
|
-
- After coordinate-based clicking or typing into an unknown focused field, take a screenshot only if verification is necessary.
|
|
57
|
-
- Use coordinates relative to the screen (0,0 is top-left).
|
|
58
|
-
- If a UI element is not visible, scroll or search for it first.
|
|
59
|
-
- macOS Accessibility permission required for mouse/keyboard control.
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class CuAgentRequest(BaseModel):
|
|
64
|
-
task: str
|
|
65
|
-
conversation_id: Optional[str] = None
|
|
66
|
-
max_steps: int = 15
|
|
67
|
-
temperature: float = 0.1
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class CuClickRequest(BaseModel):
|
|
71
|
-
x: int
|
|
72
|
-
y: int
|
|
73
|
-
button: str = "left"
|
|
74
|
-
double: bool = False
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class CuOpenAppRequest(BaseModel):
|
|
78
|
-
app: str = "Google Chrome"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class CuOpenUrlRequest(BaseModel):
|
|
82
|
-
url: str
|
|
83
|
-
app: str = "Google Chrome"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class CuTypeRequest(BaseModel):
|
|
87
|
-
text: str
|
|
88
|
-
interval: float = 0.04
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class CuKeyRequest(BaseModel):
|
|
92
|
-
key: str
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class CuScrollRequest(BaseModel):
|
|
96
|
-
x: int
|
|
97
|
-
y: int
|
|
98
|
-
direction: str = "down"
|
|
99
|
-
clicks: int = 3
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class CuMoveRequest(BaseModel):
|
|
103
|
-
x: int
|
|
104
|
-
y: int
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
class CuDragRequest(BaseModel):
|
|
108
|
-
x1: int
|
|
109
|
-
y1: int
|
|
110
|
-
x2: int
|
|
111
|
-
y2: int
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def create_computer_use_router(
|
|
115
|
-
*,
|
|
116
|
-
model_router,
|
|
117
|
-
require_user,
|
|
118
|
-
tool_response,
|
|
119
|
-
save_to_history,
|
|
120
|
-
hooks=None,
|
|
121
|
-
append_audit_event=None,
|
|
122
|
-
workspace_service=None,
|
|
123
|
-
) -> APIRouter:
|
|
124
|
-
router = APIRouter()
|
|
125
|
-
|
|
126
|
-
def _write_workspace(request: Request, current_user: str) -> Optional[str]:
|
|
127
|
-
"""Resolve only the CU agent's durable write scope.
|
|
128
|
-
|
|
129
|
-
The other ``/cu`` actions remain host operations and do not acquire a
|
|
130
|
-
workspace dependency. A no-auth local request without an explicit
|
|
131
|
-
scope preserves its legacy unscoped history behavior — that is exactly
|
|
132
|
-
what ``allow_unscoped_anonymous`` names in the shared resolver;
|
|
133
|
-
explicit scopes and every authenticated request go through the normal
|
|
134
|
-
write gate.
|
|
135
|
-
"""
|
|
136
|
-
|
|
137
|
-
return resolve_workspace_scope(
|
|
138
|
-
request,
|
|
139
|
-
user=current_user,
|
|
140
|
-
workspace_service=workspace_service,
|
|
141
|
-
write=True,
|
|
142
|
-
allow_unscoped_anonymous=True,
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
def _audit_args(name: str, args: dict) -> dict:
|
|
146
|
-
"""Return audit-safe argument metadata without storing typed text."""
|
|
147
|
-
args = dict(args or {})
|
|
148
|
-
if name == "computer_type":
|
|
149
|
-
return {"text_length": len(str(args.get("text") or "")), "interval": args.get("interval")}
|
|
150
|
-
if name == "vision_analyze":
|
|
151
|
-
return {
|
|
152
|
-
"image_b64_length": len(str(args.get("image_b64") or "")),
|
|
153
|
-
"prompt_length": len(str(args.get("prompt") or "")),
|
|
154
|
-
}
|
|
155
|
-
return args
|
|
156
|
-
|
|
157
|
-
def _audit(name: str, args: dict, *, current_user: str, status: str, error: Optional[str] = None) -> None:
|
|
158
|
-
if append_audit_event is None:
|
|
159
|
-
return
|
|
160
|
-
payload = {
|
|
161
|
-
"user_email": current_user,
|
|
162
|
-
"source": "computer_use",
|
|
163
|
-
"tool": name,
|
|
164
|
-
"status": status,
|
|
165
|
-
"args": _audit_args(name, args),
|
|
166
|
-
}
|
|
167
|
-
if error:
|
|
168
|
-
payload["error"] = error[:500]
|
|
169
|
-
append_audit_event("computer_use_tool", **payload)
|
|
170
|
-
|
|
171
|
-
def _dispatch(name, args, fn, *, current_user: str):
|
|
172
|
-
# Run a computer-use action through the unified pre_tool/post_tool
|
|
173
|
-
# lifecycle after the same ToolRegistry policy gate used by direct
|
|
174
|
-
# /tools execution. This keeps desktop control behind one governance
|
|
175
|
-
# boundary instead of relying on route-specific checks.
|
|
176
|
-
args = dict(args or {})
|
|
177
|
-
try:
|
|
178
|
-
enforce_tool_policy(name, args, current_user=current_user, source="computer_use")
|
|
179
|
-
except HTTPException as exc:
|
|
180
|
-
_audit(name, args, current_user=current_user, status="blocked", error=str(exc.detail))
|
|
181
|
-
raise
|
|
182
|
-
try:
|
|
183
|
-
result = dispatch_tool(hooks, name, args, fn, source="computer_use")
|
|
184
|
-
except (PermissionError, ToolError, KeyError, TypeError) as exc:
|
|
185
|
-
_audit(name, args, current_user=current_user, status="error", error=str(exc))
|
|
186
|
-
raise
|
|
187
|
-
_audit(name, args, current_user=current_user, status="ok")
|
|
188
|
-
return result
|
|
189
|
-
|
|
190
|
-
def _response(result):
|
|
191
|
-
return {"status": "ok", "workspace": str(AGENT_ROOT), "result": result}
|
|
192
|
-
|
|
193
|
-
@router.get("/tools/chrome_status")
|
|
194
|
-
async def tools_chrome_status(request: Request):
|
|
195
|
-
current_user = require_user(request)
|
|
196
|
-
result = _dispatch(
|
|
197
|
-
"chrome_status",
|
|
198
|
-
{},
|
|
199
|
-
desktop_bridge_status,
|
|
200
|
-
current_user=current_user,
|
|
201
|
-
)
|
|
202
|
-
return _response(result)
|
|
203
|
-
|
|
204
|
-
@router.get("/tools/computer_use_status")
|
|
205
|
-
async def tools_computer_use_status(request: Request):
|
|
206
|
-
current_user = require_user(request)
|
|
207
|
-
result = _dispatch(
|
|
208
|
-
"computer_use_status",
|
|
209
|
-
{},
|
|
210
|
-
computer_status,
|
|
211
|
-
current_user=current_user,
|
|
212
|
-
)
|
|
213
|
-
return _response(result)
|
|
214
|
-
|
|
215
|
-
@router.get("/cu/status")
|
|
216
|
-
async def cu_status(request: Request):
|
|
217
|
-
current_user = require_user(request)
|
|
218
|
-
try:
|
|
219
|
-
return _dispatch("computer_status", {}, computer_status, current_user=current_user)
|
|
220
|
-
except HTTPException:
|
|
221
|
-
raise
|
|
222
|
-
except PermissionError as exc:
|
|
223
|
-
raise HTTPException(status_code=403, detail=str(exc))
|
|
224
|
-
except ToolError as exc:
|
|
225
|
-
raise HTTPException(status_code=400, detail=str(exc))
|
|
226
|
-
|
|
227
|
-
@router.get("/cu/screenshot")
|
|
228
|
-
async def cu_screenshot(request: Request):
|
|
229
|
-
current_user = require_user(request)
|
|
230
|
-
try:
|
|
231
|
-
return _dispatch("computer_screenshot", {}, computer_screenshot, current_user=current_user)
|
|
232
|
-
except HTTPException:
|
|
233
|
-
raise
|
|
234
|
-
except PermissionError as exc:
|
|
235
|
-
raise HTTPException(status_code=403, detail=str(exc))
|
|
236
|
-
except ToolError as exc:
|
|
237
|
-
raise HTTPException(status_code=400, detail=str(exc))
|
|
238
|
-
|
|
239
|
-
@router.post("/cu/open_app")
|
|
240
|
-
async def cu_open_app(req: CuOpenAppRequest, request: Request):
|
|
241
|
-
current_user = require_user(request)
|
|
242
|
-
result = _dispatch(
|
|
243
|
-
"computer_open_app",
|
|
244
|
-
{"app": req.app},
|
|
245
|
-
lambda: computer_open_app(req.app),
|
|
246
|
-
current_user=current_user,
|
|
247
|
-
)
|
|
248
|
-
return _response(result)
|
|
249
|
-
|
|
250
|
-
@router.post("/cu/open_url")
|
|
251
|
-
async def cu_open_url(req: CuOpenUrlRequest, request: Request):
|
|
252
|
-
current_user = require_user(request)
|
|
253
|
-
result = _dispatch(
|
|
254
|
-
"computer_open_url",
|
|
255
|
-
{"url": req.url, "app": req.app},
|
|
256
|
-
lambda: computer_open_url(req.url, req.app),
|
|
257
|
-
current_user=current_user,
|
|
258
|
-
)
|
|
259
|
-
return _response(result)
|
|
260
|
-
|
|
261
|
-
@router.post("/cu/click")
|
|
262
|
-
async def cu_click(req: CuClickRequest, request: Request):
|
|
263
|
-
current_user = require_user(request)
|
|
264
|
-
result = _dispatch(
|
|
265
|
-
"computer_click",
|
|
266
|
-
{"x": req.x, "y": req.y, "button": req.button, "double": req.double},
|
|
267
|
-
lambda: computer_click(req.x, req.y, req.button, req.double),
|
|
268
|
-
current_user=current_user,
|
|
269
|
-
)
|
|
270
|
-
return _response(result)
|
|
271
|
-
|
|
272
|
-
@router.post("/cu/type")
|
|
273
|
-
async def cu_type(req: CuTypeRequest, request: Request):
|
|
274
|
-
current_user = require_user(request)
|
|
275
|
-
result = _dispatch(
|
|
276
|
-
"computer_type",
|
|
277
|
-
{"text": req.text, "interval": req.interval},
|
|
278
|
-
lambda: computer_type(req.text, req.interval),
|
|
279
|
-
current_user=current_user,
|
|
280
|
-
)
|
|
281
|
-
return _response(result)
|
|
282
|
-
|
|
283
|
-
@router.post("/cu/key")
|
|
284
|
-
async def cu_key(req: CuKeyRequest, request: Request):
|
|
285
|
-
current_user = require_user(request)
|
|
286
|
-
result = _dispatch(
|
|
287
|
-
"computer_key",
|
|
288
|
-
{"key": req.key},
|
|
289
|
-
lambda: computer_key(req.key),
|
|
290
|
-
current_user=current_user,
|
|
291
|
-
)
|
|
292
|
-
return _response(result)
|
|
293
|
-
|
|
294
|
-
@router.post("/cu/scroll")
|
|
295
|
-
async def cu_scroll(req: CuScrollRequest, request: Request):
|
|
296
|
-
current_user = require_user(request)
|
|
297
|
-
result = _dispatch(
|
|
298
|
-
"computer_scroll",
|
|
299
|
-
{"x": req.x, "y": req.y, "direction": req.direction, "clicks": req.clicks},
|
|
300
|
-
lambda: computer_scroll(req.x, req.y, req.direction, req.clicks),
|
|
301
|
-
current_user=current_user,
|
|
302
|
-
)
|
|
303
|
-
return _response(result)
|
|
304
|
-
|
|
305
|
-
@router.post("/cu/move")
|
|
306
|
-
async def cu_move(req: CuMoveRequest, request: Request):
|
|
307
|
-
current_user = require_user(request)
|
|
308
|
-
result = _dispatch(
|
|
309
|
-
"computer_move",
|
|
310
|
-
{"x": req.x, "y": req.y},
|
|
311
|
-
lambda: computer_move(req.x, req.y),
|
|
312
|
-
current_user=current_user,
|
|
313
|
-
)
|
|
314
|
-
return _response(result)
|
|
315
|
-
|
|
316
|
-
@router.post("/cu/drag")
|
|
317
|
-
async def cu_drag(req: CuDragRequest, request: Request):
|
|
318
|
-
current_user = require_user(request)
|
|
319
|
-
result = _dispatch(
|
|
320
|
-
"computer_drag",
|
|
321
|
-
{"x1": req.x1, "y1": req.y1, "x2": req.x2, "y2": req.y2},
|
|
322
|
-
lambda: computer_drag(req.x1, req.y1, req.x2, req.y2),
|
|
323
|
-
current_user=current_user,
|
|
324
|
-
)
|
|
325
|
-
return _response(result)
|
|
326
|
-
|
|
327
|
-
@router.post("/cu/agent")
|
|
328
|
-
async def cu_agent(req: CuAgentRequest, request: Request):
|
|
329
|
-
current_user = require_user(request)
|
|
330
|
-
workspace_id = _write_workspace(request, current_user)
|
|
331
|
-
|
|
332
|
-
def _save_completed(role: str, message: str) -> None:
|
|
333
|
-
save_to_history(
|
|
334
|
-
role,
|
|
335
|
-
message,
|
|
336
|
-
source="web",
|
|
337
|
-
conversation_id=req.conversation_id,
|
|
338
|
-
user_email=current_user,
|
|
339
|
-
workspace_id=workspace_id,
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
async def _stream():
|
|
343
|
-
task_lower = (req.task or "").lower()
|
|
344
|
-
url_match = re.search(r"(https?://[^\s]+|localhost:\d+[^\s]*|127\.0\.0\.1:\d+[^\s]*)", req.task or "")
|
|
345
|
-
|
|
346
|
-
if ("chrome" in task_lower or "크롬" in task_lower) and any(
|
|
347
|
-
word in task_lower for word in ["open", "열", "켜", "실행", "띄"]
|
|
348
|
-
):
|
|
349
|
-
yield sse_frame("start", {"task": req.task, "max_steps": 1})
|
|
350
|
-
try:
|
|
351
|
-
if url_match:
|
|
352
|
-
url = url_match.group(1)
|
|
353
|
-
yield sse_frame(
|
|
354
|
-
"action",
|
|
355
|
-
{"step": 1, "action": "computer_open_url", "args": {"url": url, "app": "Google Chrome"}},
|
|
356
|
-
)
|
|
357
|
-
result = _dispatch(
|
|
358
|
-
"computer_open_url",
|
|
359
|
-
{"url": url, "app": "Google Chrome"},
|
|
360
|
-
lambda: computer_open_url(url, "Google Chrome"),
|
|
361
|
-
current_user=current_user,
|
|
362
|
-
)
|
|
363
|
-
yield sse_frame("result", {"step": 1, "action": "computer_open_url", "result": result})
|
|
364
|
-
message = f"Google Chrome에서 {url}을 열었습니다."
|
|
365
|
-
action_name = "computer_open_url"
|
|
366
|
-
else:
|
|
367
|
-
yield sse_frame(
|
|
368
|
-
"action",
|
|
369
|
-
{"step": 1, "action": "computer_open_app", "args": {"app": "Google Chrome"}},
|
|
370
|
-
)
|
|
371
|
-
result = _dispatch(
|
|
372
|
-
"computer_open_app",
|
|
373
|
-
{"app": "Google Chrome"},
|
|
374
|
-
lambda: computer_open_app("Google Chrome"),
|
|
375
|
-
current_user=current_user,
|
|
376
|
-
)
|
|
377
|
-
yield sse_frame("result", {"step": 1, "action": "computer_open_app", "result": result})
|
|
378
|
-
message = "Google Chrome을 열었습니다."
|
|
379
|
-
action_name = "computer_open_app"
|
|
380
|
-
_save_completed("user", req.task)
|
|
381
|
-
_save_completed("assistant", message)
|
|
382
|
-
yield sse_frame("final", {"message": message, "steps": [{"step": 1, "action": action_name, "result": result}]})
|
|
383
|
-
except HTTPException as exc:
|
|
384
|
-
yield sse_frame("tool_error", {"step": 1, "action": "computer_open_app", "error": str(exc.detail)})
|
|
385
|
-
except (ToolError, PermissionError) as exc:
|
|
386
|
-
yield sse_frame("tool_error", {"step": 1, "action": "computer_open_app", "error": str(exc)})
|
|
387
|
-
return
|
|
388
|
-
|
|
389
|
-
if not model_router.current_model_id:
|
|
390
|
-
yield sse_frame("error", {"error": "No model loaded."})
|
|
391
|
-
return
|
|
392
|
-
|
|
393
|
-
transcript: List[Dict[str, Any]] = []
|
|
394
|
-
last_screenshot_b64: Optional[str] = None
|
|
395
|
-
max_steps = max(1, min(req.max_steps, 20))
|
|
396
|
-
yield sse_frame("start", {"task": req.task, "max_steps": max_steps})
|
|
397
|
-
|
|
398
|
-
for step in range(max_steps):
|
|
399
|
-
context = (
|
|
400
|
-
f"{CU_SYSTEM_PROMPT}\n\n"
|
|
401
|
-
f"Task: {req.task}\n\n"
|
|
402
|
-
f"Steps completed so far:\n{json.dumps(transcript, ensure_ascii=False, indent=2)}"
|
|
403
|
-
)
|
|
404
|
-
raw = await model_router.generate(
|
|
405
|
-
message="Choose the next computer use action.",
|
|
406
|
-
context=context,
|
|
407
|
-
image_data=last_screenshot_b64,
|
|
408
|
-
max_tokens=1024,
|
|
409
|
-
temperature=req.temperature,
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
try:
|
|
413
|
-
action = _extract_agent_action(str(raw))
|
|
414
|
-
except ValueError as exc:
|
|
415
|
-
yield sse_frame("error", {"step": step + 1, "error": str(exc), "raw": str(raw)})
|
|
416
|
-
break
|
|
417
|
-
|
|
418
|
-
name = action.get("action")
|
|
419
|
-
args = action.get("args") or {}
|
|
420
|
-
if name == "final":
|
|
421
|
-
message = action.get("message", "작업을 완료했습니다.")
|
|
422
|
-
_save_completed("user", req.task)
|
|
423
|
-
_save_completed("assistant", message)
|
|
424
|
-
yield sse_frame("final", {"message": message, "steps": transcript})
|
|
425
|
-
return
|
|
426
|
-
|
|
427
|
-
yield sse_frame("action", {"step": step + 1, "action": name, "args": args})
|
|
428
|
-
try:
|
|
429
|
-
result = _dispatch(
|
|
430
|
-
name,
|
|
431
|
-
args,
|
|
432
|
-
# Bound as defaults; see chat_agent_http for why.
|
|
433
|
-
lambda name=name, args=args: execute_tool(name, args),
|
|
434
|
-
current_user=current_user,
|
|
435
|
-
)
|
|
436
|
-
if name == "computer_screenshot" and "screenshot_b64" in result:
|
|
437
|
-
last_screenshot_b64 = result["screenshot_b64"]
|
|
438
|
-
result_summary = {k: v for k, v in result.items() if k != "screenshot_b64"}
|
|
439
|
-
result_summary["screenshot_captured"] = True
|
|
440
|
-
transcript.append({"step": step + 1, "action": name, "args": args, "result": result_summary})
|
|
441
|
-
yield sse_frame(
|
|
442
|
-
"screenshot",
|
|
443
|
-
{
|
|
444
|
-
"step": step + 1,
|
|
445
|
-
"screenshot_b64": last_screenshot_b64,
|
|
446
|
-
"width": result.get("screen_width"),
|
|
447
|
-
"height": result.get("screen_height"),
|
|
448
|
-
},
|
|
449
|
-
)
|
|
450
|
-
else:
|
|
451
|
-
last_screenshot_b64 = None
|
|
452
|
-
transcript.append({"step": step + 1, "action": name, "args": args, "result": result})
|
|
453
|
-
yield sse_frame("result", {"step": step + 1, "action": name, "result": result})
|
|
454
|
-
except HTTPException as exc:
|
|
455
|
-
error_str = str(exc.detail)
|
|
456
|
-
transcript.append({"step": step + 1, "action": name, "args": args, "error": error_str})
|
|
457
|
-
yield sse_frame("tool_error", {"step": step + 1, "action": name, "error": error_str})
|
|
458
|
-
except (ToolError, PermissionError, KeyError, TypeError) as exc:
|
|
459
|
-
error_str = str(exc)
|
|
460
|
-
transcript.append({"step": step + 1, "action": name, "args": args, "error": error_str})
|
|
461
|
-
yield sse_frame("tool_error", {"step": step + 1, "action": name, "error": error_str})
|
|
462
|
-
|
|
463
|
-
yield sse_frame("done", {"steps": len(transcript), "transcript": transcript})
|
|
464
|
-
|
|
465
|
-
return StreamingResponse(
|
|
466
|
-
_stream(),
|
|
467
|
-
media_type="text/event-stream",
|
|
468
|
-
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
return router
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
__all__ = ["create_computer_use_router"]
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"""Evidence → action router (v9.9.6).
|
|
2
|
-
|
|
3
|
-
``POST /api/evidence/actions`` turns an answer's citations into ready-to-send,
|
|
4
|
-
evidence-scoped follow-up prompts. Read-only and deterministic: it resolves
|
|
5
|
-
graph nodes and composes text — no model call, no writes. Execution stays on
|
|
6
|
-
the existing chat / file-generation path so there is exactly one road from a
|
|
7
|
-
prompt to an artifact.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
from typing import Any, Callable, List, Optional
|
|
13
|
-
|
|
14
|
-
from fastapi import APIRouter, Request
|
|
15
|
-
from pydantic import BaseModel, Field
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class EvidenceActionsRequest(BaseModel):
|
|
19
|
-
"""Citations from one answer (the ids the grounding badge reported)."""
|
|
20
|
-
|
|
21
|
-
question: str = ""
|
|
22
|
-
source_ids: List[str] = Field(default_factory=list)
|
|
23
|
-
language: str = "ko"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def create_evidence_actions_router(
|
|
27
|
-
*,
|
|
28
|
-
service: Any,
|
|
29
|
-
require_user: Callable[[Request], Any],
|
|
30
|
-
allowed_workspaces_for: Optional[Callable[[Any], Any]] = None,
|
|
31
|
-
) -> APIRouter:
|
|
32
|
-
router = APIRouter()
|
|
33
|
-
|
|
34
|
-
@router.post("/api/evidence/actions")
|
|
35
|
-
async def evidence_actions(req: EvidenceActionsRequest, request: Request):
|
|
36
|
-
user = require_user(request)
|
|
37
|
-
scope = allowed_workspaces_for(user) if allowed_workspaces_for is not None else None
|
|
38
|
-
return service.actions_for(
|
|
39
|
-
question=req.question,
|
|
40
|
-
source_ids=req.source_ids,
|
|
41
|
-
language=req.language,
|
|
42
|
-
allowed_workspaces=scope,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
return router
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
__all__ = ["create_evidence_actions_router", "EvidenceActionsRequest"]
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"""Feature switchboard API — read the catalog, move one switch (v11.2.0).
|
|
2
|
-
|
|
3
|
-
Two routes and no more. The catalog is rendered by the service (labels,
|
|
4
|
-
explanations, defaults, which options are even installable), so this router
|
|
5
|
-
carries no product knowledge at all: adding a feature is a line in
|
|
6
|
-
``latticeai/services/feature_toggles.CATALOG``, not a change here or in the
|
|
7
|
-
client.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
from typing import Any, Callable, Union
|
|
13
|
-
|
|
14
|
-
from fastapi import APIRouter, HTTPException, Request
|
|
15
|
-
from pydantic import BaseModel, Field
|
|
16
|
-
|
|
17
|
-
from latticeai.core.messages import http_error, resolve_language
|
|
18
|
-
from latticeai.services.feature_toggles import (
|
|
19
|
-
FeatureToggleService,
|
|
20
|
-
InvalidFeatureValue,
|
|
21
|
-
UnknownFeature,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class SetFeatureRequest(BaseModel):
|
|
26
|
-
"""One switch move. ``value`` is a bool for a toggle, a string for a choice."""
|
|
27
|
-
|
|
28
|
-
value: Union[bool, str] = Field(
|
|
29
|
-
..., description="true/false for a toggle, or the option id for a choice"
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def create_features_router(
|
|
34
|
-
*,
|
|
35
|
-
service: FeatureToggleService,
|
|
36
|
-
require_user: Callable[..., str],
|
|
37
|
-
) -> APIRouter:
|
|
38
|
-
router = APIRouter(tags=["features"])
|
|
39
|
-
|
|
40
|
-
@router.get("/api/features")
|
|
41
|
-
async def list_features(request: Request) -> Any:
|
|
42
|
-
"""Every opt-in feature, its live value, and where that value came from."""
|
|
43
|
-
require_user(request)
|
|
44
|
-
return service.catalog(resolve_language(request))
|
|
45
|
-
|
|
46
|
-
@router.post("/api/features/{feature_id}")
|
|
47
|
-
async def set_feature(
|
|
48
|
-
feature_id: str, body: SetFeatureRequest, request: Request
|
|
49
|
-
) -> Any:
|
|
50
|
-
"""Persist one person's choice; the answer is the feature as it now reads."""
|
|
51
|
-
user = require_user(request)
|
|
52
|
-
language = resolve_language(request)
|
|
53
|
-
try:
|
|
54
|
-
return service.set(
|
|
55
|
-
feature_id, body.value, language=language, user_email=user
|
|
56
|
-
)
|
|
57
|
-
except UnknownFeature as exc:
|
|
58
|
-
raise http_error(
|
|
59
|
-
400, "features.unknown", language, feature=feature_id
|
|
60
|
-
) from exc
|
|
61
|
-
except InvalidFeatureValue as exc:
|
|
62
|
-
# Pass-through: the service already localized the reason through the
|
|
63
|
-
# same catalog (wrong type, or an option whose optional dependency
|
|
64
|
-
# is not installed), and re-wording it here would only lose it.
|
|
65
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
66
|
-
|
|
67
|
-
return router
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
__all__ = ["SetFeatureRequest", "create_features_router"]
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"""Admin-only UX funnel metrics endpoint (backlog #16).
|
|
2
|
-
|
|
3
|
-
Exposes :class:`~latticeai.services.funnel_metrics.FunnelMetricsService`
|
|
4
|
-
snapshots at ``GET /api/admin/funnel-metrics``. Follows the admin router
|
|
5
|
-
convention: gate through the injected ``require_admin`` callable, read-only,
|
|
6
|
-
no side effects.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
from typing import Any, Callable
|
|
12
|
-
|
|
13
|
-
from fastapi import APIRouter, Request
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def create_funnel_metrics_router(
|
|
17
|
-
*,
|
|
18
|
-
service: Any,
|
|
19
|
-
require_admin: Callable[[Request], Any],
|
|
20
|
-
) -> APIRouter:
|
|
21
|
-
router = APIRouter()
|
|
22
|
-
|
|
23
|
-
@router.get("/api/admin/funnel-metrics")
|
|
24
|
-
async def funnel_metrics(request: Request):
|
|
25
|
-
require_admin(request)
|
|
26
|
-
return service.snapshot()
|
|
27
|
-
|
|
28
|
-
return router
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
__all__ = ["create_funnel_metrics_router"]
|
package/latticeai/api/garden.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"""P-Reinforce garden routes."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
from fastapi import APIRouter, Request
|
|
8
|
-
from pydantic import BaseModel
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class GardenRequest(BaseModel):
|
|
12
|
-
raw_data: str
|
|
13
|
-
category: Optional[str] = None
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def create_garden_router(*, gardener, require_user) -> APIRouter:
|
|
17
|
-
api_router = APIRouter()
|
|
18
|
-
|
|
19
|
-
# ── P-Reinforce Knowledge Gardener ────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
@api_router.post("/garden")
|
|
22
|
-
async def garden(req: GardenRequest, request: Request):
|
|
23
|
-
"""Raw 데이터를 P-Reinforce 구조로 자동 분류·저장"""
|
|
24
|
-
require_user(request)
|
|
25
|
-
result = await gardener.process(req.raw_data, req.category)
|
|
26
|
-
return result
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@api_router.get("/garden/tree")
|
|
30
|
-
async def garden_tree(request: Request):
|
|
31
|
-
"""지식 정원 파일트리 반환"""
|
|
32
|
-
require_user(request)
|
|
33
|
-
return gardener.get_tree()
|
|
34
|
-
return api_router
|